@burdenoff/mcp-libs 2026.627.4 → 2026.627.5
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/modules/mybodyshield/index.cjs +1834 -0
- package/dist/modules/mybodyshield/index.cjs.map +1 -0
- package/dist/modules/mybodyshield/index.d.cts +8 -0
- package/dist/modules/mybodyshield/index.d.ts +8 -0
- package/dist/modules/mybodyshield/index.js +1773 -0
- package/dist/modules/mybodyshield/index.js.map +1 -0
- package/dist/modules/semanticfed/index.cjs +1846 -0
- package/dist/modules/semanticfed/index.cjs.map +1 -0
- package/dist/modules/semanticfed/index.d.cts +8 -0
- package/dist/modules/semanticfed/index.d.ts +8 -0
- package/dist/modules/semanticfed/index.js +1785 -0
- package/dist/modules/semanticfed/index.js.map +1 -0
- package/dist/modules/technospam/index.cjs +1962 -0
- package/dist/modules/technospam/index.cjs.map +1 -0
- package/dist/modules/technospam/index.d.cts +8 -0
- package/dist/modules/technospam/index.d.ts +8 -0
- package/dist/modules/technospam/index.js +1901 -0
- package/dist/modules/technospam/index.js.map +1 -0
- package/dist/modules/theculturepost/index.cjs +1587 -0
- package/dist/modules/theculturepost/index.cjs.map +1 -0
- package/dist/modules/theculturepost/index.d.cts +8 -0
- package/dist/modules/theculturepost/index.d.ts +8 -0
- package/dist/modules/theculturepost/index.js +1526 -0
- package/dist/modules/theculturepost/index.js.map +1 -0
- package/dist/modules/theglobalfuel/index.cjs +1678 -0
- package/dist/modules/theglobalfuel/index.cjs.map +1 -0
- package/dist/modules/theglobalfuel/index.d.cts +8 -0
- package/dist/modules/theglobalfuel/index.d.ts +8 -0
- package/dist/modules/theglobalfuel/index.js +1617 -0
- package/dist/modules/theglobalfuel/index.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,1834 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var zod = require('zod');
|
|
4
|
+
require('graphql-request');
|
|
5
|
+
require('cross-fetch');
|
|
6
|
+
require('@modelcontextprotocol/sdk/server/mcp.js');
|
|
7
|
+
require('@modelcontextprotocol/sdk/types.js');
|
|
8
|
+
|
|
9
|
+
// src/modules/mybodyshield/tools/programs.ts
|
|
10
|
+
|
|
11
|
+
// src/core/errors.ts
|
|
12
|
+
var McpInternalError = class extends Error {
|
|
13
|
+
cause;
|
|
14
|
+
constructor(message, cause) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.name = "McpInternalError";
|
|
17
|
+
this.cause = cause;
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// src/core/result.ts
|
|
22
|
+
function jsonResult(data, structured) {
|
|
23
|
+
const result = {
|
|
24
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
25
|
+
};
|
|
26
|
+
return result;
|
|
27
|
+
}
|
|
28
|
+
function errorResult(message, hint) {
|
|
29
|
+
const text = hint ? `${message}
|
|
30
|
+
|
|
31
|
+
Hint: ${hint}` : message;
|
|
32
|
+
return { content: [{ type: "text", text }], isError: true };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// src/core/tool-name.ts
|
|
36
|
+
var MAX_TOOL_NAME_LENGTH = 63;
|
|
37
|
+
function assertSafeName(name, kind) {
|
|
38
|
+
if (name.length > MAX_TOOL_NAME_LENGTH) {
|
|
39
|
+
throw new McpInternalError(
|
|
40
|
+
`Tool name '${name}' exceeds ${MAX_TOOL_NAME_LENGTH} characters (some MCP clients truncate at 64).`
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
return name;
|
|
44
|
+
}
|
|
45
|
+
function mcpToolName(productName, ...parts) {
|
|
46
|
+
if (!productName) {
|
|
47
|
+
throw new McpInternalError("mcpToolName: productName is required");
|
|
48
|
+
}
|
|
49
|
+
const name = [productName, ...parts].filter(Boolean).join("_");
|
|
50
|
+
return assertSafeName(name);
|
|
51
|
+
}
|
|
52
|
+
function mcpResourceUri(productName, ...parts) {
|
|
53
|
+
if (!productName) {
|
|
54
|
+
throw new McpInternalError("mcpResourceUri: productName is required");
|
|
55
|
+
}
|
|
56
|
+
return `${productName}://${parts.filter(Boolean).join("/")}`;
|
|
57
|
+
}
|
|
58
|
+
function mcpPromptName(productName, ...parts) {
|
|
59
|
+
if (!productName) {
|
|
60
|
+
throw new McpInternalError("mcpPromptName: productName is required");
|
|
61
|
+
}
|
|
62
|
+
return [productName, ...parts].filter(Boolean).join("-");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// src/modules/mybodyshield/operations.ts
|
|
66
|
+
var PAGE_INFO_FIELDS = `
|
|
67
|
+
cursor hasMore
|
|
68
|
+
`;
|
|
69
|
+
var EXERCISE_FIELDS = `
|
|
70
|
+
id workoutId name instructions muscleGroup sets reps restSeconds targetWeight
|
|
71
|
+
orderIndex demo { id } createdAt updatedAt workspaceId
|
|
72
|
+
`;
|
|
73
|
+
var WORKOUT_FIELDS = `
|
|
74
|
+
id programId name description type dayOfWeek weekNumber durationMin orderIndex
|
|
75
|
+
createdAt updatedAt workspaceId
|
|
76
|
+
`;
|
|
77
|
+
var PROGRAM_FIELDS = `
|
|
78
|
+
id name description goalType level phase status memberId coachId daysPerWeek
|
|
79
|
+
weeks startDate endDate tags createdAt updatedAt workspaceId
|
|
80
|
+
`;
|
|
81
|
+
var SESSION_SET_FIELDS = `
|
|
82
|
+
id sessionId exerciseName setNumber reps weight distanceM durationSec
|
|
83
|
+
isPersonalRecord createdAt updatedAt workspaceId
|
|
84
|
+
`;
|
|
85
|
+
var SESSION_FIELDS = `
|
|
86
|
+
id memberId programId workoutId status scheduledFor startedAt completedAt
|
|
87
|
+
caloriesBurned perceivedExertion notes createdAt updatedAt workspaceId
|
|
88
|
+
`;
|
|
89
|
+
var MEAL_FIELDS = `
|
|
90
|
+
id name memberId planId mealType calories proteinG carbsG fatG servings
|
|
91
|
+
isFavorite loggedAt createdAt updatedAt workspaceId
|
|
92
|
+
`;
|
|
93
|
+
var NUTRITION_PLAN_FIELDS = `
|
|
94
|
+
id name memberId coachId goalType calorieTarget proteinTarget carbsTarget
|
|
95
|
+
fatTarget startDate endDate isActive notes createdAt updatedAt workspaceId
|
|
96
|
+
`;
|
|
97
|
+
var DAILY_MACROS_FIELDS = `
|
|
98
|
+
date calories calorieTarget proteinG proteinTarget carbsG carbsTarget
|
|
99
|
+
fatG fatTarget
|
|
100
|
+
`;
|
|
101
|
+
var GOAL_FIELDS = `
|
|
102
|
+
id title description type status memberId baselineValue currentValue
|
|
103
|
+
targetValue unit targetDate achievedAt createdAt updatedAt workspaceId
|
|
104
|
+
`;
|
|
105
|
+
var BODY_METRIC_FIELDS = `
|
|
106
|
+
id memberId type value unit measuredAt notes photo { id }
|
|
107
|
+
createdAt updatedAt workspaceId
|
|
108
|
+
`;
|
|
109
|
+
var RECOVERY_FIELDS = `
|
|
110
|
+
id memberId metric score sleepHours notes loggedAt createdAt updatedAt workspaceId
|
|
111
|
+
`;
|
|
112
|
+
var COACHING_FIELDS = `
|
|
113
|
+
id coachId memberId status specialty shareWorkouts shareNutrition shareMetrics
|
|
114
|
+
notes startedAt endedAt createdAt updatedAt workspaceId
|
|
115
|
+
`;
|
|
116
|
+
var GET_PROGRAM = `
|
|
117
|
+
query MybodyshieldGetProgram($id: ID!) {
|
|
118
|
+
mybProgram(id: $id) {
|
|
119
|
+
${PROGRAM_FIELDS}
|
|
120
|
+
workouts {
|
|
121
|
+
${WORKOUT_FIELDS}
|
|
122
|
+
exercises { ${EXERCISE_FIELDS} }
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
`;
|
|
127
|
+
var LIST_PROGRAMS = `
|
|
128
|
+
query MybodyshieldListPrograms(
|
|
129
|
+
$memberId: ID
|
|
130
|
+
$status: MybProgramStatus
|
|
131
|
+
$pagination: PaginationInput
|
|
132
|
+
) {
|
|
133
|
+
mybListPrograms(memberId: $memberId, status: $status, pagination: $pagination) {
|
|
134
|
+
items { ${PROGRAM_FIELDS} }
|
|
135
|
+
pageInfo { ${PAGE_INFO_FIELDS} }
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
`;
|
|
139
|
+
var CREATE_PROGRAM = `
|
|
140
|
+
mutation MybodyshieldCreateProgram($input: MybCreateProgramInput!) {
|
|
141
|
+
mybCreateProgram(input: $input) { ${PROGRAM_FIELDS} }
|
|
142
|
+
}
|
|
143
|
+
`;
|
|
144
|
+
var UPDATE_PROGRAM = `
|
|
145
|
+
mutation MybodyshieldUpdateProgram($id: ID!, $input: MybUpdateProgramInput!) {
|
|
146
|
+
mybUpdateProgram(id: $id, input: $input) { ${PROGRAM_FIELDS} }
|
|
147
|
+
}
|
|
148
|
+
`;
|
|
149
|
+
var TRANSITION_PROGRAM = `
|
|
150
|
+
mutation MybodyshieldTransitionProgram($id: ID!, $status: MybProgramStatus!) {
|
|
151
|
+
mybTransitionProgram(id: $id, status: $status) { ${PROGRAM_FIELDS} }
|
|
152
|
+
}
|
|
153
|
+
`;
|
|
154
|
+
var ARCHIVE_PROGRAM = `
|
|
155
|
+
mutation MybodyshieldArchiveProgram($id: ID!) {
|
|
156
|
+
mybArchiveProgram(id: $id)
|
|
157
|
+
}
|
|
158
|
+
`;
|
|
159
|
+
var LIST_PROGRAM_WORKOUTS = `
|
|
160
|
+
query MybodyshieldListProgramWorkouts($programId: ID!) {
|
|
161
|
+
mybProgramWorkouts(programId: $programId) {
|
|
162
|
+
${WORKOUT_FIELDS}
|
|
163
|
+
exercises { ${EXERCISE_FIELDS} }
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
`;
|
|
167
|
+
var ADD_WORKOUT = `
|
|
168
|
+
mutation MybodyshieldAddWorkout($input: MybCreateWorkoutInput!) {
|
|
169
|
+
mybAddWorkout(input: $input) { ${WORKOUT_FIELDS} }
|
|
170
|
+
}
|
|
171
|
+
`;
|
|
172
|
+
var ADD_EXERCISE = `
|
|
173
|
+
mutation MybodyshieldAddExercise($input: MybCreateExerciseInput!) {
|
|
174
|
+
mybAddExercise(input: $input) { ${EXERCISE_FIELDS} }
|
|
175
|
+
}
|
|
176
|
+
`;
|
|
177
|
+
var GET_SESSION = `
|
|
178
|
+
query MybodyshieldGetSession($id: ID!) {
|
|
179
|
+
mybSession(id: $id) {
|
|
180
|
+
${SESSION_FIELDS}
|
|
181
|
+
sets { ${SESSION_SET_FIELDS} }
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
`;
|
|
185
|
+
var LIST_SESSIONS = `
|
|
186
|
+
query MybodyshieldListSessions(
|
|
187
|
+
$programId: ID
|
|
188
|
+
$status: MybSessionStatus
|
|
189
|
+
$pagination: PaginationInput
|
|
190
|
+
) {
|
|
191
|
+
mybListSessions(programId: $programId, status: $status, pagination: $pagination) {
|
|
192
|
+
items {
|
|
193
|
+
${SESSION_FIELDS}
|
|
194
|
+
sets { ${SESSION_SET_FIELDS} }
|
|
195
|
+
}
|
|
196
|
+
pageInfo { ${PAGE_INFO_FIELDS} }
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
`;
|
|
200
|
+
var START_SESSION = `
|
|
201
|
+
mutation MybodyshieldStartSession($input: MybStartSessionInput!) {
|
|
202
|
+
mybStartSession(input: $input) {
|
|
203
|
+
${SESSION_FIELDS}
|
|
204
|
+
sets { ${SESSION_SET_FIELDS} }
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
`;
|
|
208
|
+
var COMPLETE_SESSION = `
|
|
209
|
+
mutation MybodyshieldCompleteSession($id: ID!, $input: MybCompleteSessionInput!) {
|
|
210
|
+
mybCompleteSession(id: $id, input: $input) {
|
|
211
|
+
${SESSION_FIELDS}
|
|
212
|
+
sets { ${SESSION_SET_FIELDS} }
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
`;
|
|
216
|
+
var LOG_SET = `
|
|
217
|
+
mutation MybodyshieldLogSet($input: MybLogSetInput!) {
|
|
218
|
+
mybLogSet(input: $input) { ${SESSION_SET_FIELDS} }
|
|
219
|
+
}
|
|
220
|
+
`;
|
|
221
|
+
var GET_NUTRITION_PLAN = `
|
|
222
|
+
query MybodyshieldGetNutritionPlan($id: ID!) {
|
|
223
|
+
mybNutritionPlan(id: $id) {
|
|
224
|
+
${NUTRITION_PLAN_FIELDS}
|
|
225
|
+
meals { ${MEAL_FIELDS} }
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
`;
|
|
229
|
+
var ACTIVE_NUTRITION_PLAN = `
|
|
230
|
+
query MybodyshieldActiveNutritionPlan($memberId: ID) {
|
|
231
|
+
mybActiveNutritionPlan(memberId: $memberId) {
|
|
232
|
+
${NUTRITION_PLAN_FIELDS}
|
|
233
|
+
meals { ${MEAL_FIELDS} }
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
`;
|
|
237
|
+
var CREATE_NUTRITION_PLAN = `
|
|
238
|
+
mutation MybodyshieldCreateNutritionPlan($input: MybCreateNutritionPlanInput!) {
|
|
239
|
+
mybCreateNutritionPlan(input: $input) { ${NUTRITION_PLAN_FIELDS} }
|
|
240
|
+
}
|
|
241
|
+
`;
|
|
242
|
+
var UPDATE_NUTRITION_PLAN = `
|
|
243
|
+
mutation MybodyshieldUpdateNutritionPlan(
|
|
244
|
+
$id: ID!
|
|
245
|
+
$input: MybUpdateNutritionPlanInput!
|
|
246
|
+
) {
|
|
247
|
+
mybUpdateNutritionPlan(id: $id, input: $input) { ${NUTRITION_PLAN_FIELDS} }
|
|
248
|
+
}
|
|
249
|
+
`;
|
|
250
|
+
var ACTIVATE_NUTRITION_PLAN = `
|
|
251
|
+
mutation MybodyshieldActivateNutritionPlan($id: ID!) {
|
|
252
|
+
mybActivateNutritionPlan(id: $id) { ${NUTRITION_PLAN_FIELDS} }
|
|
253
|
+
}
|
|
254
|
+
`;
|
|
255
|
+
var DAILY_MACROS = `
|
|
256
|
+
query MybodyshieldDailyMacros($memberId: ID, $date: DateTime) {
|
|
257
|
+
mybDailyMacros(memberId: $memberId, date: $date) { ${DAILY_MACROS_FIELDS} }
|
|
258
|
+
}
|
|
259
|
+
`;
|
|
260
|
+
var LIST_MEALS = `
|
|
261
|
+
query MybodyshieldListMeals(
|
|
262
|
+
$planId: ID
|
|
263
|
+
$mealType: MybMealType
|
|
264
|
+
$pagination: PaginationInput
|
|
265
|
+
) {
|
|
266
|
+
mybListMeals(planId: $planId, mealType: $mealType, pagination: $pagination) {
|
|
267
|
+
items { ${MEAL_FIELDS} }
|
|
268
|
+
pageInfo { ${PAGE_INFO_FIELDS} }
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
`;
|
|
272
|
+
var LOG_MEAL = `
|
|
273
|
+
mutation MybodyshieldLogMeal($input: MybLogMealInput!) {
|
|
274
|
+
mybLogMeal(input: $input) { ${MEAL_FIELDS} }
|
|
275
|
+
}
|
|
276
|
+
`;
|
|
277
|
+
var DELETE_MEAL = `
|
|
278
|
+
mutation MybodyshieldDeleteMeal($id: ID!) {
|
|
279
|
+
mybDeleteMeal(id: $id)
|
|
280
|
+
}
|
|
281
|
+
`;
|
|
282
|
+
var GET_GOAL = `
|
|
283
|
+
query MybodyshieldGetGoal($id: ID!) {
|
|
284
|
+
mybGoal(id: $id) { ${GOAL_FIELDS} }
|
|
285
|
+
}
|
|
286
|
+
`;
|
|
287
|
+
var LIST_GOALS = `
|
|
288
|
+
query MybodyshieldListGoals(
|
|
289
|
+
$memberId: ID
|
|
290
|
+
$status: MybGoalStatus
|
|
291
|
+
$pagination: PaginationInput
|
|
292
|
+
) {
|
|
293
|
+
mybListGoals(memberId: $memberId, status: $status, pagination: $pagination) {
|
|
294
|
+
items { ${GOAL_FIELDS} }
|
|
295
|
+
pageInfo { ${PAGE_INFO_FIELDS} }
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
`;
|
|
299
|
+
var CREATE_GOAL = `
|
|
300
|
+
mutation MybodyshieldCreateGoal($input: MybCreateGoalInput!) {
|
|
301
|
+
mybCreateGoal(input: $input) { ${GOAL_FIELDS} }
|
|
302
|
+
}
|
|
303
|
+
`;
|
|
304
|
+
var UPDATE_GOAL = `
|
|
305
|
+
mutation MybodyshieldUpdateGoal($id: ID!, $input: MybUpdateGoalInput!) {
|
|
306
|
+
mybUpdateGoal(id: $id, input: $input) { ${GOAL_FIELDS} }
|
|
307
|
+
}
|
|
308
|
+
`;
|
|
309
|
+
var ACHIEVE_GOAL = `
|
|
310
|
+
mutation MybodyshieldAchieveGoal($id: ID!) {
|
|
311
|
+
mybAchieveGoal(id: $id) { ${GOAL_FIELDS} }
|
|
312
|
+
}
|
|
313
|
+
`;
|
|
314
|
+
var LIST_BODY_METRICS = `
|
|
315
|
+
query MybodyshieldListBodyMetrics(
|
|
316
|
+
$memberId: ID
|
|
317
|
+
$type: MybBodyMetricType
|
|
318
|
+
$pagination: PaginationInput
|
|
319
|
+
) {
|
|
320
|
+
mybListBodyMetrics(memberId: $memberId, type: $type, pagination: $pagination) {
|
|
321
|
+
items { ${BODY_METRIC_FIELDS} }
|
|
322
|
+
pageInfo { ${PAGE_INFO_FIELDS} }
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
`;
|
|
326
|
+
var RECORD_BODY_METRIC = `
|
|
327
|
+
mutation MybodyshieldRecordBodyMetric($input: MybRecordMetricInput!) {
|
|
328
|
+
mybRecordBodyMetric(input: $input) { ${BODY_METRIC_FIELDS} }
|
|
329
|
+
}
|
|
330
|
+
`;
|
|
331
|
+
var LIST_RECOVERY_LOGS = `
|
|
332
|
+
query MybodyshieldListRecoveryLogs($memberId: ID, $metric: MybRecoveryMetric) {
|
|
333
|
+
mybListRecoveryLogs(memberId: $memberId, metric: $metric) { ${RECOVERY_FIELDS} }
|
|
334
|
+
}
|
|
335
|
+
`;
|
|
336
|
+
var LOG_RECOVERY = `
|
|
337
|
+
mutation MybodyshieldLogRecovery($input: MybLogRecoveryInput!) {
|
|
338
|
+
mybLogRecovery(input: $input) { ${RECOVERY_FIELDS} }
|
|
339
|
+
}
|
|
340
|
+
`;
|
|
341
|
+
var LIST_COACHING = `
|
|
342
|
+
query MybodyshieldListCoaching($memberId: ID, $status: MybCoachingStatus) {
|
|
343
|
+
mybListCoaching(memberId: $memberId, status: $status) { ${COACHING_FIELDS} }
|
|
344
|
+
}
|
|
345
|
+
`;
|
|
346
|
+
var CREATE_COACHING = `
|
|
347
|
+
mutation MybodyshieldCreateCoaching($input: MybCreateCoachingInput!) {
|
|
348
|
+
mybCreateCoaching(input: $input) { ${COACHING_FIELDS} }
|
|
349
|
+
}
|
|
350
|
+
`;
|
|
351
|
+
var TRANSITION_COACHING = `
|
|
352
|
+
mutation MybodyshieldTransitionCoaching($id: ID!, $status: MybCoachingStatus!) {
|
|
353
|
+
mybTransitionCoaching(id: $id, status: $status) { ${COACHING_FIELDS} }
|
|
354
|
+
}
|
|
355
|
+
`;
|
|
356
|
+
var HEALTH = `
|
|
357
|
+
query MybodyshieldHealth {
|
|
358
|
+
mybHealth
|
|
359
|
+
}
|
|
360
|
+
`;
|
|
361
|
+
var GoalTypeEnum = zod.z.enum(["ENDURANCE", "GENERAL_FITNESS", "MOBILITY", "MUSCLE_GAIN", "STRENGTH", "WEIGHT_LOSS"]).describe("Training/nutrition goal type (MybGoalType)");
|
|
362
|
+
var GoalStatusEnum = zod.z.enum(["ABANDONED", "ACHIEVED", "ACTIVE", "PAUSED"]).describe("Lifecycle status of a goal (MybGoalStatus)");
|
|
363
|
+
var FitnessLevelEnum = zod.z.enum(["ADVANCED", "BEGINNER", "ELITE", "INTERMEDIATE"]).describe("Member fitness level for a program (MybFitnessLevel)");
|
|
364
|
+
var ProgramPhaseEnum = zod.z.enum(["BUILD", "DELOAD", "MAINTENANCE", "PEAK"]).describe("Periodization phase of a program (MybProgramPhase)");
|
|
365
|
+
var ProgramStatusEnum = zod.z.enum(["ACTIVE", "ARCHIVED", "COMPLETED", "DRAFT"]).describe("Lifecycle status of a training program (MybProgramStatus)");
|
|
366
|
+
var WorkoutTypeEnum = zod.z.enum(["CARDIO", "HIIT", "MOBILITY", "RECOVERY", "STRENGTH", "YOGA"]).describe("Type of a workout (MybWorkoutType)");
|
|
367
|
+
var MuscleGroupEnum = zod.z.enum(["ARMS", "BACK", "CHEST", "CORE", "FULL_BODY", "LEGS", "SHOULDERS"]).describe("Primary muscle group an exercise targets (MybMuscleGroup)");
|
|
368
|
+
var SessionStatusEnum = zod.z.enum(["COMPLETED", "IN_PROGRESS", "SCHEDULED", "SKIPPED"]).describe("Status of a logged workout session (MybSessionStatus)");
|
|
369
|
+
var MealTypeEnum = zod.z.enum(["BREAKFAST", "DINNER", "LUNCH", "SNACK"]).describe("Meal slot a meal log belongs to (MybMealType)");
|
|
370
|
+
var BodyMetricTypeEnum = zod.z.enum(["ARM", "BODY_FAT", "CHEST", "HIPS", "RESTING_HEART_RATE", "THIGH", "WAIST", "WEIGHT"]).describe("Kind of body measurement (MybBodyMetricType)");
|
|
371
|
+
var RecoveryMetricEnum = zod.z.enum(["MOOD", "READINESS", "SLEEP", "SORENESS", "STRESS"]).describe("Kind of recovery/wellbeing signal (MybRecoveryMetric)");
|
|
372
|
+
var CoachingStatusEnum = zod.z.enum(["ACTIVE", "ENDED", "PAUSED", "PENDING"]).describe("Status of a coach\u2194member relationship (MybCoachingStatus)");
|
|
373
|
+
|
|
374
|
+
// src/modules/mybodyshield/tools/programs.ts
|
|
375
|
+
function registerProgramTools(server, ctx, track) {
|
|
376
|
+
server.registerTool(
|
|
377
|
+
track(mcpToolName(ctx.productName, "programs_get")),
|
|
378
|
+
{
|
|
379
|
+
title: "Get Training Program",
|
|
380
|
+
description: "Fetch a single Mybodyshield training program by ID, including its nested workouts and exercises.",
|
|
381
|
+
inputSchema: { id: zod.z.string().describe("Program ID") }
|
|
382
|
+
},
|
|
383
|
+
async ({ id }) => {
|
|
384
|
+
try {
|
|
385
|
+
const data = await ctx.gql.workspace.request(GET_PROGRAM, { id });
|
|
386
|
+
if (!data.mybProgram) return errorResult(`Program ${id} not found`);
|
|
387
|
+
return jsonResult(data.mybProgram);
|
|
388
|
+
} catch (error) {
|
|
389
|
+
ctx.logger.error("mybodyshield programs_get failed", { error: String(error) });
|
|
390
|
+
return errorResult(
|
|
391
|
+
`Failed to get program: ${error instanceof Error ? error.message : String(error)}`
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
);
|
|
396
|
+
server.registerTool(
|
|
397
|
+
track(mcpToolName(ctx.productName, "programs_list")),
|
|
398
|
+
{
|
|
399
|
+
title: "List Training Programs",
|
|
400
|
+
description: "List training programs in the bound workspace, optionally filtered by member and lifecycle status. Returns a paginated connection ({ items, pageInfo }).",
|
|
401
|
+
inputSchema: {
|
|
402
|
+
memberId: zod.z.string().optional().describe("Filter by member (user) ID"),
|
|
403
|
+
status: ProgramStatusEnum.optional().describe("Filter by program status"),
|
|
404
|
+
limit: zod.z.number().int().optional().describe("Page size (default 50)"),
|
|
405
|
+
offset: zod.z.number().int().optional().describe("Pagination offset (default 0)"),
|
|
406
|
+
cursor: zod.z.string().optional().describe("Opaque pagination cursor")
|
|
407
|
+
}
|
|
408
|
+
},
|
|
409
|
+
async ({ memberId, status, limit, offset, cursor }) => {
|
|
410
|
+
try {
|
|
411
|
+
const data = await ctx.gql.workspace.request(LIST_PROGRAMS, {
|
|
412
|
+
memberId,
|
|
413
|
+
status,
|
|
414
|
+
pagination: { limit: limit ?? 50, offset: offset ?? 0, cursor }
|
|
415
|
+
});
|
|
416
|
+
return jsonResult(data.mybListPrograms);
|
|
417
|
+
} catch (error) {
|
|
418
|
+
ctx.logger.error("mybodyshield programs_list failed", { error: String(error) });
|
|
419
|
+
return errorResult(
|
|
420
|
+
`Failed to list programs: ${error instanceof Error ? error.message : String(error)}`
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
);
|
|
425
|
+
server.registerTool(
|
|
426
|
+
track(mcpToolName(ctx.productName, "programs_create")),
|
|
427
|
+
{
|
|
428
|
+
title: "Create Training Program",
|
|
429
|
+
description: "Create a training program. `name` is required; goalType/level/phase default to GENERAL_FITNESS/BEGINNER/BUILD when omitted.",
|
|
430
|
+
inputSchema: {
|
|
431
|
+
name: zod.z.string().min(1).describe("Program name"),
|
|
432
|
+
memberId: zod.z.string().optional().describe("Member (user) ID the program is for"),
|
|
433
|
+
coachId: zod.z.string().optional().describe("Coach (user) ID authoring the program"),
|
|
434
|
+
description: zod.z.string().optional().describe("Program description"),
|
|
435
|
+
goalType: GoalTypeEnum.optional().describe("Primary goal (default GENERAL_FITNESS)"),
|
|
436
|
+
level: FitnessLevelEnum.optional().describe("Target fitness level (default BEGINNER)"),
|
|
437
|
+
phase: ProgramPhaseEnum.optional().describe("Periodization phase (default BUILD)"),
|
|
438
|
+
daysPerWeek: zod.z.number().int().optional().describe("Training days per week"),
|
|
439
|
+
weeks: zod.z.number().int().optional().describe("Program length in weeks"),
|
|
440
|
+
startDate: zod.z.string().optional().describe("Start date (ISO-8601 DateTime)"),
|
|
441
|
+
endDate: zod.z.string().optional().describe("End date (ISO-8601 DateTime)"),
|
|
442
|
+
tags: zod.z.array(zod.z.string()).optional().describe("Free-form tags")
|
|
443
|
+
}
|
|
444
|
+
},
|
|
445
|
+
async ({
|
|
446
|
+
name,
|
|
447
|
+
memberId,
|
|
448
|
+
coachId,
|
|
449
|
+
description,
|
|
450
|
+
goalType,
|
|
451
|
+
level,
|
|
452
|
+
phase,
|
|
453
|
+
daysPerWeek,
|
|
454
|
+
weeks,
|
|
455
|
+
startDate,
|
|
456
|
+
endDate,
|
|
457
|
+
tags
|
|
458
|
+
}) => {
|
|
459
|
+
try {
|
|
460
|
+
const input = { name };
|
|
461
|
+
if (memberId !== void 0) input.memberId = memberId;
|
|
462
|
+
if (coachId !== void 0) input.coachId = coachId;
|
|
463
|
+
if (description !== void 0) input.description = description;
|
|
464
|
+
if (goalType !== void 0) input.goalType = goalType;
|
|
465
|
+
if (level !== void 0) input.level = level;
|
|
466
|
+
if (phase !== void 0) input.phase = phase;
|
|
467
|
+
if (daysPerWeek !== void 0) input.daysPerWeek = daysPerWeek;
|
|
468
|
+
if (weeks !== void 0) input.weeks = weeks;
|
|
469
|
+
if (startDate !== void 0) input.startDate = startDate;
|
|
470
|
+
if (endDate !== void 0) input.endDate = endDate;
|
|
471
|
+
if (tags !== void 0) input.tags = tags;
|
|
472
|
+
const data = await ctx.gql.workspace.request(CREATE_PROGRAM, { input });
|
|
473
|
+
return jsonResult(data.mybCreateProgram);
|
|
474
|
+
} catch (error) {
|
|
475
|
+
ctx.logger.error("mybodyshield programs_create failed", { error: String(error) });
|
|
476
|
+
return errorResult(
|
|
477
|
+
`Failed to create program: ${error instanceof Error ? error.message : String(error)}`
|
|
478
|
+
);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
);
|
|
482
|
+
server.registerTool(
|
|
483
|
+
track(mcpToolName(ctx.productName, "programs_update")),
|
|
484
|
+
{
|
|
485
|
+
title: "Update Training Program",
|
|
486
|
+
description: "Update a training program. At least one mutable field must be provided. Use `programs_transition` for lifecycle changes.",
|
|
487
|
+
inputSchema: {
|
|
488
|
+
id: zod.z.string().describe("Program ID"),
|
|
489
|
+
name: zod.z.string().optional().describe("Updated name"),
|
|
490
|
+
description: zod.z.string().optional().describe("Updated description"),
|
|
491
|
+
coachId: zod.z.string().optional().describe("Updated coach (user) ID"),
|
|
492
|
+
goalType: GoalTypeEnum.optional().describe("Updated primary goal"),
|
|
493
|
+
level: FitnessLevelEnum.optional().describe("Updated fitness level"),
|
|
494
|
+
phase: ProgramPhaseEnum.optional().describe("Updated periodization phase"),
|
|
495
|
+
daysPerWeek: zod.z.number().int().optional().describe("Updated training days per week"),
|
|
496
|
+
weeks: zod.z.number().int().optional().describe("Updated program length in weeks"),
|
|
497
|
+
startDate: zod.z.string().optional().describe("Updated start date (ISO-8601 DateTime)"),
|
|
498
|
+
endDate: zod.z.string().optional().describe("Updated end date (ISO-8601 DateTime)"),
|
|
499
|
+
tags: zod.z.array(zod.z.string()).optional().describe("Updated tags")
|
|
500
|
+
}
|
|
501
|
+
},
|
|
502
|
+
async ({
|
|
503
|
+
id,
|
|
504
|
+
name,
|
|
505
|
+
description,
|
|
506
|
+
coachId,
|
|
507
|
+
goalType,
|
|
508
|
+
level,
|
|
509
|
+
phase,
|
|
510
|
+
daysPerWeek,
|
|
511
|
+
weeks,
|
|
512
|
+
startDate,
|
|
513
|
+
endDate,
|
|
514
|
+
tags
|
|
515
|
+
}) => {
|
|
516
|
+
try {
|
|
517
|
+
const input = {};
|
|
518
|
+
if (name !== void 0) input.name = name;
|
|
519
|
+
if (description !== void 0) input.description = description;
|
|
520
|
+
if (coachId !== void 0) input.coachId = coachId;
|
|
521
|
+
if (goalType !== void 0) input.goalType = goalType;
|
|
522
|
+
if (level !== void 0) input.level = level;
|
|
523
|
+
if (phase !== void 0) input.phase = phase;
|
|
524
|
+
if (daysPerWeek !== void 0) input.daysPerWeek = daysPerWeek;
|
|
525
|
+
if (weeks !== void 0) input.weeks = weeks;
|
|
526
|
+
if (startDate !== void 0) input.startDate = startDate;
|
|
527
|
+
if (endDate !== void 0) input.endDate = endDate;
|
|
528
|
+
if (tags !== void 0) input.tags = tags;
|
|
529
|
+
if (Object.keys(input).length === 0) {
|
|
530
|
+
return errorResult(
|
|
531
|
+
"No updates specified \u2014 provide at least one of name/description/coachId/goalType/level/phase/daysPerWeek/weeks/startDate/endDate/tags"
|
|
532
|
+
);
|
|
533
|
+
}
|
|
534
|
+
const data = await ctx.gql.workspace.request(UPDATE_PROGRAM, { id, input });
|
|
535
|
+
return jsonResult(data.mybUpdateProgram);
|
|
536
|
+
} catch (error) {
|
|
537
|
+
ctx.logger.error("mybodyshield programs_update failed", { error: String(error) });
|
|
538
|
+
return errorResult(
|
|
539
|
+
`Failed to update program: ${error instanceof Error ? error.message : String(error)}`
|
|
540
|
+
);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
);
|
|
544
|
+
server.registerTool(
|
|
545
|
+
track(mcpToolName(ctx.productName, "programs_transition")),
|
|
546
|
+
{
|
|
547
|
+
title: "Transition Training Program",
|
|
548
|
+
description: "Advance a program through its lifecycle (validated transitions): DRAFT \u2192 ACTIVE \u2192 COMPLETED / ARCHIVED.",
|
|
549
|
+
inputSchema: {
|
|
550
|
+
id: zod.z.string().describe("Program ID"),
|
|
551
|
+
status: ProgramStatusEnum.describe("Target lifecycle status")
|
|
552
|
+
}
|
|
553
|
+
},
|
|
554
|
+
async ({ id, status }) => {
|
|
555
|
+
try {
|
|
556
|
+
const data = await ctx.gql.workspace.request(TRANSITION_PROGRAM, { id, status });
|
|
557
|
+
return jsonResult(data.mybTransitionProgram);
|
|
558
|
+
} catch (error) {
|
|
559
|
+
ctx.logger.error("mybodyshield programs_transition failed", { error: String(error) });
|
|
560
|
+
return errorResult(
|
|
561
|
+
`Failed to transition program: ${error instanceof Error ? error.message : String(error)}`
|
|
562
|
+
);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
);
|
|
566
|
+
server.registerTool(
|
|
567
|
+
track(mcpToolName(ctx.productName, "programs_archive")),
|
|
568
|
+
{
|
|
569
|
+
title: "Archive Training Program",
|
|
570
|
+
description: "Archive a training program by ID. Returns true on success.",
|
|
571
|
+
inputSchema: { id: zod.z.string().describe("Program ID") }
|
|
572
|
+
},
|
|
573
|
+
async ({ id }) => {
|
|
574
|
+
try {
|
|
575
|
+
const data = await ctx.gql.workspace.request(
|
|
576
|
+
ARCHIVE_PROGRAM,
|
|
577
|
+
{ id }
|
|
578
|
+
);
|
|
579
|
+
return jsonResult({ archived: data.mybArchiveProgram });
|
|
580
|
+
} catch (error) {
|
|
581
|
+
ctx.logger.error("mybodyshield programs_archive failed", { error: String(error) });
|
|
582
|
+
return errorResult(
|
|
583
|
+
`Failed to archive program: ${error instanceof Error ? error.message : String(error)}`
|
|
584
|
+
);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
);
|
|
588
|
+
}
|
|
589
|
+
function registerWorkoutTools(server, ctx, track) {
|
|
590
|
+
server.registerTool(
|
|
591
|
+
track(mcpToolName(ctx.productName, "workouts_list")),
|
|
592
|
+
{
|
|
593
|
+
title: "List Program Workouts",
|
|
594
|
+
description: "List the workouts for a program (ordered), each with its nested exercises.",
|
|
595
|
+
inputSchema: { programId: zod.z.string().describe("Program ID") }
|
|
596
|
+
},
|
|
597
|
+
async ({ programId }) => {
|
|
598
|
+
try {
|
|
599
|
+
const data = await ctx.gql.workspace.request(LIST_PROGRAM_WORKOUTS, { programId });
|
|
600
|
+
return jsonResult(data.mybProgramWorkouts);
|
|
601
|
+
} catch (error) {
|
|
602
|
+
ctx.logger.error("mybodyshield workouts_list failed", { error: String(error) });
|
|
603
|
+
return errorResult(
|
|
604
|
+
`Failed to list program workouts: ${error instanceof Error ? error.message : String(error)}`
|
|
605
|
+
);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
);
|
|
609
|
+
server.registerTool(
|
|
610
|
+
track(mcpToolName(ctx.productName, "workouts_add")),
|
|
611
|
+
{
|
|
612
|
+
title: "Add Workout",
|
|
613
|
+
description: "Add a workout to a program. `programId` and `name` are required; `type` defaults to STRENGTH.",
|
|
614
|
+
inputSchema: {
|
|
615
|
+
programId: zod.z.string().describe("Parent program ID"),
|
|
616
|
+
name: zod.z.string().min(1).describe("Workout name"),
|
|
617
|
+
description: zod.z.string().optional().describe("Workout description"),
|
|
618
|
+
type: WorkoutTypeEnum.optional().describe("Workout type (default STRENGTH)"),
|
|
619
|
+
dayOfWeek: zod.z.number().int().optional().describe("Day of week (1-7)"),
|
|
620
|
+
weekNumber: zod.z.number().int().optional().describe("Program week number"),
|
|
621
|
+
durationMin: zod.z.number().int().optional().describe("Planned duration in minutes"),
|
|
622
|
+
orderIndex: zod.z.number().int().optional().describe("Ordering within the program")
|
|
623
|
+
}
|
|
624
|
+
},
|
|
625
|
+
async ({
|
|
626
|
+
programId,
|
|
627
|
+
name,
|
|
628
|
+
description,
|
|
629
|
+
type,
|
|
630
|
+
dayOfWeek,
|
|
631
|
+
weekNumber,
|
|
632
|
+
durationMin,
|
|
633
|
+
orderIndex
|
|
634
|
+
}) => {
|
|
635
|
+
try {
|
|
636
|
+
const input = { programId, name };
|
|
637
|
+
if (description !== void 0) input.description = description;
|
|
638
|
+
if (type !== void 0) input.type = type;
|
|
639
|
+
if (dayOfWeek !== void 0) input.dayOfWeek = dayOfWeek;
|
|
640
|
+
if (weekNumber !== void 0) input.weekNumber = weekNumber;
|
|
641
|
+
if (durationMin !== void 0) input.durationMin = durationMin;
|
|
642
|
+
if (orderIndex !== void 0) input.orderIndex = orderIndex;
|
|
643
|
+
const data = await ctx.gql.workspace.request(ADD_WORKOUT, { input });
|
|
644
|
+
return jsonResult(data.mybAddWorkout);
|
|
645
|
+
} catch (error) {
|
|
646
|
+
ctx.logger.error("mybodyshield workouts_add failed", { error: String(error) });
|
|
647
|
+
return errorResult(
|
|
648
|
+
`Failed to add workout: ${error instanceof Error ? error.message : String(error)}`
|
|
649
|
+
);
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
);
|
|
653
|
+
server.registerTool(
|
|
654
|
+
track(mcpToolName(ctx.productName, "exercises_add")),
|
|
655
|
+
{
|
|
656
|
+
title: "Add Exercise",
|
|
657
|
+
description: "Add an exercise to a workout. `workoutId` and `name` are required; `muscleGroup` defaults to FULL_BODY. `demoFileId` references a wspace-files-svc File (demo video/image).",
|
|
658
|
+
inputSchema: {
|
|
659
|
+
workoutId: zod.z.string().describe("Parent workout ID"),
|
|
660
|
+
name: zod.z.string().min(1).describe("Exercise name"),
|
|
661
|
+
instructions: zod.z.string().optional().describe("How-to instructions"),
|
|
662
|
+
muscleGroup: MuscleGroupEnum.optional().describe("Target muscle group (default FULL_BODY)"),
|
|
663
|
+
sets: zod.z.number().int().optional().describe("Number of sets"),
|
|
664
|
+
reps: zod.z.number().int().optional().describe("Reps per set"),
|
|
665
|
+
restSeconds: zod.z.number().int().optional().describe("Rest between sets (seconds)"),
|
|
666
|
+
targetWeight: zod.z.number().optional().describe("Target working weight"),
|
|
667
|
+
orderIndex: zod.z.number().int().optional().describe("Ordering within the workout"),
|
|
668
|
+
demoFileId: zod.z.string().optional().describe("wspace-files-svc File ID for the demo media")
|
|
669
|
+
}
|
|
670
|
+
},
|
|
671
|
+
async ({
|
|
672
|
+
workoutId,
|
|
673
|
+
name,
|
|
674
|
+
instructions,
|
|
675
|
+
muscleGroup,
|
|
676
|
+
sets,
|
|
677
|
+
reps,
|
|
678
|
+
restSeconds,
|
|
679
|
+
targetWeight,
|
|
680
|
+
orderIndex,
|
|
681
|
+
demoFileId
|
|
682
|
+
}) => {
|
|
683
|
+
try {
|
|
684
|
+
const input = { workoutId, name };
|
|
685
|
+
if (instructions !== void 0) input.instructions = instructions;
|
|
686
|
+
if (muscleGroup !== void 0) input.muscleGroup = muscleGroup;
|
|
687
|
+
if (sets !== void 0) input.sets = sets;
|
|
688
|
+
if (reps !== void 0) input.reps = reps;
|
|
689
|
+
if (restSeconds !== void 0) input.restSeconds = restSeconds;
|
|
690
|
+
if (targetWeight !== void 0) input.targetWeight = targetWeight;
|
|
691
|
+
if (orderIndex !== void 0) input.orderIndex = orderIndex;
|
|
692
|
+
if (demoFileId !== void 0) input.demoFileId = demoFileId;
|
|
693
|
+
const data = await ctx.gql.workspace.request(ADD_EXERCISE, { input });
|
|
694
|
+
return jsonResult(data.mybAddExercise);
|
|
695
|
+
} catch (error) {
|
|
696
|
+
ctx.logger.error("mybodyshield exercises_add failed", { error: String(error) });
|
|
697
|
+
return errorResult(
|
|
698
|
+
`Failed to add exercise: ${error instanceof Error ? error.message : String(error)}`
|
|
699
|
+
);
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
);
|
|
703
|
+
}
|
|
704
|
+
function registerSessionTools(server, ctx, track) {
|
|
705
|
+
server.registerTool(
|
|
706
|
+
track(mcpToolName(ctx.productName, "sessions_get")),
|
|
707
|
+
{
|
|
708
|
+
title: "Get Workout Session",
|
|
709
|
+
description: "Fetch a single workout session by ID, including its logged sets.",
|
|
710
|
+
inputSchema: { id: zod.z.string().describe("Workout session ID") }
|
|
711
|
+
},
|
|
712
|
+
async ({ id }) => {
|
|
713
|
+
try {
|
|
714
|
+
const data = await ctx.gql.workspace.request(GET_SESSION, { id });
|
|
715
|
+
if (!data.mybSession) return errorResult(`Session ${id} not found`);
|
|
716
|
+
return jsonResult(data.mybSession);
|
|
717
|
+
} catch (error) {
|
|
718
|
+
ctx.logger.error("mybodyshield sessions_get failed", { error: String(error) });
|
|
719
|
+
return errorResult(
|
|
720
|
+
`Failed to get session: ${error instanceof Error ? error.message : String(error)}`
|
|
721
|
+
);
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
);
|
|
725
|
+
server.registerTool(
|
|
726
|
+
track(mcpToolName(ctx.productName, "sessions_list")),
|
|
727
|
+
{
|
|
728
|
+
title: "List Workout Sessions",
|
|
729
|
+
description: "List workout sessions in the bound workspace, optionally filtered by program and status. Returns a paginated connection ({ items, pageInfo }).",
|
|
730
|
+
inputSchema: {
|
|
731
|
+
programId: zod.z.string().optional().describe("Filter by program ID"),
|
|
732
|
+
status: SessionStatusEnum.optional().describe("Filter by session status"),
|
|
733
|
+
limit: zod.z.number().int().optional().describe("Page size (default 50)"),
|
|
734
|
+
offset: zod.z.number().int().optional().describe("Pagination offset (default 0)"),
|
|
735
|
+
cursor: zod.z.string().optional().describe("Opaque pagination cursor")
|
|
736
|
+
}
|
|
737
|
+
},
|
|
738
|
+
async ({ programId, status, limit, offset, cursor }) => {
|
|
739
|
+
try {
|
|
740
|
+
const data = await ctx.gql.workspace.request(LIST_SESSIONS, {
|
|
741
|
+
programId,
|
|
742
|
+
status,
|
|
743
|
+
pagination: { limit: limit ?? 50, offset: offset ?? 0, cursor }
|
|
744
|
+
});
|
|
745
|
+
return jsonResult(data.mybListSessions);
|
|
746
|
+
} catch (error) {
|
|
747
|
+
ctx.logger.error("mybodyshield sessions_list failed", { error: String(error) });
|
|
748
|
+
return errorResult(
|
|
749
|
+
`Failed to list sessions: ${error instanceof Error ? error.message : String(error)}`
|
|
750
|
+
);
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
);
|
|
754
|
+
server.registerTool(
|
|
755
|
+
track(mcpToolName(ctx.productName, "sessions_start")),
|
|
756
|
+
{
|
|
757
|
+
title: "Start Workout Session",
|
|
758
|
+
description: "Start (or schedule) a workout session. All fields are optional \u2014 supply `workoutId`/`programId` to bind the session and `scheduledFor` to plan it for later.",
|
|
759
|
+
inputSchema: {
|
|
760
|
+
workoutId: zod.z.string().optional().describe("Workout being performed"),
|
|
761
|
+
programId: zod.z.string().optional().describe("Program the session belongs to"),
|
|
762
|
+
scheduledFor: zod.z.string().optional().describe("Scheduled time (ISO-8601 DateTime)"),
|
|
763
|
+
notes: zod.z.string().optional().describe("Session notes")
|
|
764
|
+
}
|
|
765
|
+
},
|
|
766
|
+
async ({ workoutId, programId, scheduledFor, notes }) => {
|
|
767
|
+
try {
|
|
768
|
+
const input = {};
|
|
769
|
+
if (workoutId !== void 0) input.workoutId = workoutId;
|
|
770
|
+
if (programId !== void 0) input.programId = programId;
|
|
771
|
+
if (scheduledFor !== void 0) input.scheduledFor = scheduledFor;
|
|
772
|
+
if (notes !== void 0) input.notes = notes;
|
|
773
|
+
const data = await ctx.gql.workspace.request(START_SESSION, { input });
|
|
774
|
+
return jsonResult(data.mybStartSession);
|
|
775
|
+
} catch (error) {
|
|
776
|
+
ctx.logger.error("mybodyshield sessions_start failed", { error: String(error) });
|
|
777
|
+
return errorResult(
|
|
778
|
+
`Failed to start session: ${error instanceof Error ? error.message : String(error)}`
|
|
779
|
+
);
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
);
|
|
783
|
+
server.registerTool(
|
|
784
|
+
track(mcpToolName(ctx.productName, "sessions_complete")),
|
|
785
|
+
{
|
|
786
|
+
title: "Complete Workout Session",
|
|
787
|
+
description: "Mark a workout session complete, recording calories burned, perceived exertion (RPE), and notes.",
|
|
788
|
+
inputSchema: {
|
|
789
|
+
id: zod.z.string().describe("Workout session ID"),
|
|
790
|
+
caloriesBurned: zod.z.number().int().optional().describe("Estimated calories burned"),
|
|
791
|
+
perceivedExertion: zod.z.number().int().optional().describe("Perceived exertion / RPE"),
|
|
792
|
+
notes: zod.z.string().optional().describe("Completion notes")
|
|
793
|
+
}
|
|
794
|
+
},
|
|
795
|
+
async ({ id, caloriesBurned, perceivedExertion, notes }) => {
|
|
796
|
+
try {
|
|
797
|
+
const input = {};
|
|
798
|
+
if (caloriesBurned !== void 0) input.caloriesBurned = caloriesBurned;
|
|
799
|
+
if (perceivedExertion !== void 0) input.perceivedExertion = perceivedExertion;
|
|
800
|
+
if (notes !== void 0) input.notes = notes;
|
|
801
|
+
const data = await ctx.gql.workspace.request(COMPLETE_SESSION, { id, input });
|
|
802
|
+
return jsonResult(data.mybCompleteSession);
|
|
803
|
+
} catch (error) {
|
|
804
|
+
ctx.logger.error("mybodyshield sessions_complete failed", { error: String(error) });
|
|
805
|
+
return errorResult(
|
|
806
|
+
`Failed to complete session: ${error instanceof Error ? error.message : String(error)}`
|
|
807
|
+
);
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
);
|
|
811
|
+
server.registerTool(
|
|
812
|
+
track(mcpToolName(ctx.productName, "sets_log")),
|
|
813
|
+
{
|
|
814
|
+
title: "Log Session Set",
|
|
815
|
+
description: "Log a single set within a workout session. `sessionId` and `exerciseName` are required; record reps/weight (strength) or distanceM/durationSec (cardio) and flag personal records.",
|
|
816
|
+
inputSchema: {
|
|
817
|
+
sessionId: zod.z.string().describe("Parent workout session ID"),
|
|
818
|
+
exerciseName: zod.z.string().min(1).describe("Exercise name for this set"),
|
|
819
|
+
setNumber: zod.z.number().int().optional().describe("Set ordinal within the exercise"),
|
|
820
|
+
reps: zod.z.number().int().optional().describe("Repetitions performed"),
|
|
821
|
+
weight: zod.z.number().optional().describe("Weight used"),
|
|
822
|
+
distanceM: zod.z.number().optional().describe("Distance in metres (cardio)"),
|
|
823
|
+
durationSec: zod.z.number().int().optional().describe("Duration in seconds (cardio)"),
|
|
824
|
+
isPersonalRecord: zod.z.boolean().optional().describe("Mark as a personal record")
|
|
825
|
+
}
|
|
826
|
+
},
|
|
827
|
+
async ({
|
|
828
|
+
sessionId,
|
|
829
|
+
exerciseName,
|
|
830
|
+
setNumber,
|
|
831
|
+
reps,
|
|
832
|
+
weight,
|
|
833
|
+
distanceM,
|
|
834
|
+
durationSec,
|
|
835
|
+
isPersonalRecord
|
|
836
|
+
}) => {
|
|
837
|
+
try {
|
|
838
|
+
const input = { sessionId, exerciseName };
|
|
839
|
+
if (setNumber !== void 0) input.setNumber = setNumber;
|
|
840
|
+
if (reps !== void 0) input.reps = reps;
|
|
841
|
+
if (weight !== void 0) input.weight = weight;
|
|
842
|
+
if (distanceM !== void 0) input.distanceM = distanceM;
|
|
843
|
+
if (durationSec !== void 0) input.durationSec = durationSec;
|
|
844
|
+
if (isPersonalRecord !== void 0) input.isPersonalRecord = isPersonalRecord;
|
|
845
|
+
const data = await ctx.gql.workspace.request(LOG_SET, { input });
|
|
846
|
+
return jsonResult(data.mybLogSet);
|
|
847
|
+
} catch (error) {
|
|
848
|
+
ctx.logger.error("mybodyshield sets_log failed", { error: String(error) });
|
|
849
|
+
return errorResult(
|
|
850
|
+
`Failed to log set: ${error instanceof Error ? error.message : String(error)}`
|
|
851
|
+
);
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
);
|
|
855
|
+
}
|
|
856
|
+
function registerNutritionTools(server, ctx, track) {
|
|
857
|
+
server.registerTool(
|
|
858
|
+
track(mcpToolName(ctx.productName, "nutrition_get")),
|
|
859
|
+
{
|
|
860
|
+
title: "Get Nutrition Plan",
|
|
861
|
+
description: "Fetch a single nutrition plan by ID, including its logged meals.",
|
|
862
|
+
inputSchema: { id: zod.z.string().describe("Nutrition plan ID") }
|
|
863
|
+
},
|
|
864
|
+
async ({ id }) => {
|
|
865
|
+
try {
|
|
866
|
+
const data = await ctx.gql.workspace.request(GET_NUTRITION_PLAN, { id });
|
|
867
|
+
if (!data.mybNutritionPlan) return errorResult(`Nutrition plan ${id} not found`);
|
|
868
|
+
return jsonResult(data.mybNutritionPlan);
|
|
869
|
+
} catch (error) {
|
|
870
|
+
ctx.logger.error("mybodyshield nutrition_get failed", { error: String(error) });
|
|
871
|
+
return errorResult(
|
|
872
|
+
`Failed to get nutrition plan: ${error instanceof Error ? error.message : String(error)}`
|
|
873
|
+
);
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
);
|
|
877
|
+
server.registerTool(
|
|
878
|
+
track(mcpToolName(ctx.productName, "nutrition_active")),
|
|
879
|
+
{
|
|
880
|
+
title: "Get Active Nutrition Plan",
|
|
881
|
+
description: "Fetch the active nutrition plan for a member (defaults to the calling actor when memberId is omitted). Returns null when there's no active plan.",
|
|
882
|
+
inputSchema: {
|
|
883
|
+
memberId: zod.z.string().optional().describe("Member (user) ID (default: calling actor)")
|
|
884
|
+
}
|
|
885
|
+
},
|
|
886
|
+
async ({ memberId }) => {
|
|
887
|
+
try {
|
|
888
|
+
const data = await ctx.gql.workspace.request(ACTIVE_NUTRITION_PLAN, { memberId });
|
|
889
|
+
return jsonResult(data.mybActiveNutritionPlan);
|
|
890
|
+
} catch (error) {
|
|
891
|
+
ctx.logger.error("mybodyshield nutrition_active failed", { error: String(error) });
|
|
892
|
+
return errorResult(
|
|
893
|
+
`Failed to get active nutrition plan: ${error instanceof Error ? error.message : String(error)}`
|
|
894
|
+
);
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
);
|
|
898
|
+
server.registerTool(
|
|
899
|
+
track(mcpToolName(ctx.productName, "nutrition_create")),
|
|
900
|
+
{
|
|
901
|
+
title: "Create Nutrition Plan",
|
|
902
|
+
description: "Create a nutrition plan with macro targets. `name` is required; `goalType` defaults to GENERAL_FITNESS.",
|
|
903
|
+
inputSchema: {
|
|
904
|
+
name: zod.z.string().min(1).describe("Plan name"),
|
|
905
|
+
memberId: zod.z.string().optional().describe("Member (user) ID the plan is for"),
|
|
906
|
+
coachId: zod.z.string().optional().describe("Coach (user) ID authoring the plan"),
|
|
907
|
+
goalType: GoalTypeEnum.optional().describe("Goal (default GENERAL_FITNESS)"),
|
|
908
|
+
calorieTarget: zod.z.number().int().optional().describe("Daily calorie target"),
|
|
909
|
+
proteinTarget: zod.z.number().int().optional().describe("Daily protein target (g)"),
|
|
910
|
+
carbsTarget: zod.z.number().int().optional().describe("Daily carbohydrate target (g)"),
|
|
911
|
+
fatTarget: zod.z.number().int().optional().describe("Daily fat target (g)"),
|
|
912
|
+
startDate: zod.z.string().optional().describe("Start date (ISO-8601 DateTime)"),
|
|
913
|
+
endDate: zod.z.string().optional().describe("End date (ISO-8601 DateTime)"),
|
|
914
|
+
notes: zod.z.string().optional().describe("Plan notes")
|
|
915
|
+
}
|
|
916
|
+
},
|
|
917
|
+
async ({
|
|
918
|
+
name,
|
|
919
|
+
memberId,
|
|
920
|
+
coachId,
|
|
921
|
+
goalType,
|
|
922
|
+
calorieTarget,
|
|
923
|
+
proteinTarget,
|
|
924
|
+
carbsTarget,
|
|
925
|
+
fatTarget,
|
|
926
|
+
startDate,
|
|
927
|
+
endDate,
|
|
928
|
+
notes
|
|
929
|
+
}) => {
|
|
930
|
+
try {
|
|
931
|
+
const input = { name };
|
|
932
|
+
if (memberId !== void 0) input.memberId = memberId;
|
|
933
|
+
if (coachId !== void 0) input.coachId = coachId;
|
|
934
|
+
if (goalType !== void 0) input.goalType = goalType;
|
|
935
|
+
if (calorieTarget !== void 0) input.calorieTarget = calorieTarget;
|
|
936
|
+
if (proteinTarget !== void 0) input.proteinTarget = proteinTarget;
|
|
937
|
+
if (carbsTarget !== void 0) input.carbsTarget = carbsTarget;
|
|
938
|
+
if (fatTarget !== void 0) input.fatTarget = fatTarget;
|
|
939
|
+
if (startDate !== void 0) input.startDate = startDate;
|
|
940
|
+
if (endDate !== void 0) input.endDate = endDate;
|
|
941
|
+
if (notes !== void 0) input.notes = notes;
|
|
942
|
+
const data = await ctx.gql.workspace.request(CREATE_NUTRITION_PLAN, { input });
|
|
943
|
+
return jsonResult(data.mybCreateNutritionPlan);
|
|
944
|
+
} catch (error) {
|
|
945
|
+
ctx.logger.error("mybodyshield nutrition_create failed", { error: String(error) });
|
|
946
|
+
return errorResult(
|
|
947
|
+
`Failed to create nutrition plan: ${error instanceof Error ? error.message : String(error)}`
|
|
948
|
+
);
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
);
|
|
952
|
+
server.registerTool(
|
|
953
|
+
track(mcpToolName(ctx.productName, "nutrition_update")),
|
|
954
|
+
{
|
|
955
|
+
title: "Update Nutrition Plan",
|
|
956
|
+
description: "Update a nutrition plan. At least one mutable field must be provided. Use `nutrition_activate` to make a plan the active one.",
|
|
957
|
+
inputSchema: {
|
|
958
|
+
id: zod.z.string().describe("Nutrition plan ID"),
|
|
959
|
+
name: zod.z.string().optional().describe("Updated name"),
|
|
960
|
+
goalType: GoalTypeEnum.optional().describe("Updated goal"),
|
|
961
|
+
calorieTarget: zod.z.number().int().optional().describe("Updated calorie target"),
|
|
962
|
+
proteinTarget: zod.z.number().int().optional().describe("Updated protein target (g)"),
|
|
963
|
+
carbsTarget: zod.z.number().int().optional().describe("Updated carbohydrate target (g)"),
|
|
964
|
+
fatTarget: zod.z.number().int().optional().describe("Updated fat target (g)"),
|
|
965
|
+
startDate: zod.z.string().optional().describe("Updated start date (ISO-8601 DateTime)"),
|
|
966
|
+
endDate: zod.z.string().optional().describe("Updated end date (ISO-8601 DateTime)"),
|
|
967
|
+
notes: zod.z.string().optional().describe("Updated notes")
|
|
968
|
+
}
|
|
969
|
+
},
|
|
970
|
+
async ({
|
|
971
|
+
id,
|
|
972
|
+
name,
|
|
973
|
+
goalType,
|
|
974
|
+
calorieTarget,
|
|
975
|
+
proteinTarget,
|
|
976
|
+
carbsTarget,
|
|
977
|
+
fatTarget,
|
|
978
|
+
startDate,
|
|
979
|
+
endDate,
|
|
980
|
+
notes
|
|
981
|
+
}) => {
|
|
982
|
+
try {
|
|
983
|
+
const input = {};
|
|
984
|
+
if (name !== void 0) input.name = name;
|
|
985
|
+
if (goalType !== void 0) input.goalType = goalType;
|
|
986
|
+
if (calorieTarget !== void 0) input.calorieTarget = calorieTarget;
|
|
987
|
+
if (proteinTarget !== void 0) input.proteinTarget = proteinTarget;
|
|
988
|
+
if (carbsTarget !== void 0) input.carbsTarget = carbsTarget;
|
|
989
|
+
if (fatTarget !== void 0) input.fatTarget = fatTarget;
|
|
990
|
+
if (startDate !== void 0) input.startDate = startDate;
|
|
991
|
+
if (endDate !== void 0) input.endDate = endDate;
|
|
992
|
+
if (notes !== void 0) input.notes = notes;
|
|
993
|
+
if (Object.keys(input).length === 0) {
|
|
994
|
+
return errorResult(
|
|
995
|
+
"No updates specified \u2014 provide at least one of name/goalType/calorieTarget/proteinTarget/carbsTarget/fatTarget/startDate/endDate/notes"
|
|
996
|
+
);
|
|
997
|
+
}
|
|
998
|
+
const data = await ctx.gql.workspace.request(UPDATE_NUTRITION_PLAN, { id, input });
|
|
999
|
+
return jsonResult(data.mybUpdateNutritionPlan);
|
|
1000
|
+
} catch (error) {
|
|
1001
|
+
ctx.logger.error("mybodyshield nutrition_update failed", { error: String(error) });
|
|
1002
|
+
return errorResult(
|
|
1003
|
+
`Failed to update nutrition plan: ${error instanceof Error ? error.message : String(error)}`
|
|
1004
|
+
);
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
);
|
|
1008
|
+
server.registerTool(
|
|
1009
|
+
track(mcpToolName(ctx.productName, "nutrition_activate")),
|
|
1010
|
+
{
|
|
1011
|
+
title: "Activate Nutrition Plan",
|
|
1012
|
+
description: "Make a nutrition plan the active plan for its member (deactivating any other active plan).",
|
|
1013
|
+
inputSchema: { id: zod.z.string().describe("Nutrition plan ID") }
|
|
1014
|
+
},
|
|
1015
|
+
async ({ id }) => {
|
|
1016
|
+
try {
|
|
1017
|
+
const data = await ctx.gql.workspace.request(ACTIVATE_NUTRITION_PLAN, { id });
|
|
1018
|
+
return jsonResult(data.mybActivateNutritionPlan);
|
|
1019
|
+
} catch (error) {
|
|
1020
|
+
ctx.logger.error("mybodyshield nutrition_activate failed", { error: String(error) });
|
|
1021
|
+
return errorResult(
|
|
1022
|
+
`Failed to activate nutrition plan: ${error instanceof Error ? error.message : String(error)}`
|
|
1023
|
+
);
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
);
|
|
1027
|
+
server.registerTool(
|
|
1028
|
+
track(mcpToolName(ctx.productName, "nutrition_daily_macros")),
|
|
1029
|
+
{
|
|
1030
|
+
title: "Daily Macros Rollup",
|
|
1031
|
+
description: "Roll up a member's logged meals for a given day against the active plan's targets (calories + protein/carbs/fat consumed vs target).",
|
|
1032
|
+
inputSchema: {
|
|
1033
|
+
memberId: zod.z.string().optional().describe("Member (user) ID (default: calling actor)"),
|
|
1034
|
+
date: zod.z.string().optional().describe("Day to roll up (ISO-8601 DateTime; default: today)")
|
|
1035
|
+
}
|
|
1036
|
+
},
|
|
1037
|
+
async ({ memberId, date }) => {
|
|
1038
|
+
try {
|
|
1039
|
+
const data = await ctx.gql.workspace.request(DAILY_MACROS, { memberId, date });
|
|
1040
|
+
return jsonResult(data.mybDailyMacros);
|
|
1041
|
+
} catch (error) {
|
|
1042
|
+
ctx.logger.error("mybodyshield nutrition_daily_macros failed", { error: String(error) });
|
|
1043
|
+
return errorResult(
|
|
1044
|
+
`Failed to roll up daily macros: ${error instanceof Error ? error.message : String(error)}`
|
|
1045
|
+
);
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
);
|
|
1049
|
+
}
|
|
1050
|
+
function registerMealTools(server, ctx, track) {
|
|
1051
|
+
server.registerTool(
|
|
1052
|
+
track(mcpToolName(ctx.productName, "meals_list")),
|
|
1053
|
+
{
|
|
1054
|
+
title: "List Meal Logs",
|
|
1055
|
+
description: "List logged meals in the bound workspace, optionally filtered by nutrition plan and meal type. Returns a paginated connection ({ items, pageInfo }).",
|
|
1056
|
+
inputSchema: {
|
|
1057
|
+
planId: zod.z.string().optional().describe("Filter by nutrition plan ID"),
|
|
1058
|
+
mealType: MealTypeEnum.optional().describe("Filter by meal slot"),
|
|
1059
|
+
limit: zod.z.number().int().optional().describe("Page size (default 50)"),
|
|
1060
|
+
offset: zod.z.number().int().optional().describe("Pagination offset (default 0)"),
|
|
1061
|
+
cursor: zod.z.string().optional().describe("Opaque pagination cursor")
|
|
1062
|
+
}
|
|
1063
|
+
},
|
|
1064
|
+
async ({ planId, mealType, limit, offset, cursor }) => {
|
|
1065
|
+
try {
|
|
1066
|
+
const data = await ctx.gql.workspace.request(LIST_MEALS, {
|
|
1067
|
+
planId,
|
|
1068
|
+
mealType,
|
|
1069
|
+
pagination: { limit: limit ?? 50, offset: offset ?? 0, cursor }
|
|
1070
|
+
});
|
|
1071
|
+
return jsonResult(data.mybListMeals);
|
|
1072
|
+
} catch (error) {
|
|
1073
|
+
ctx.logger.error("mybodyshield meals_list failed", { error: String(error) });
|
|
1074
|
+
return errorResult(
|
|
1075
|
+
`Failed to list meals: ${error instanceof Error ? error.message : String(error)}`
|
|
1076
|
+
);
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
);
|
|
1080
|
+
server.registerTool(
|
|
1081
|
+
track(mcpToolName(ctx.productName, "meals_log")),
|
|
1082
|
+
{
|
|
1083
|
+
title: "Log Meal",
|
|
1084
|
+
description: "Log a meal with its macros. `name` is required; `mealType` defaults to SNACK. Attach to a plan via `planId` to count toward daily macros.",
|
|
1085
|
+
inputSchema: {
|
|
1086
|
+
name: zod.z.string().min(1).describe("Meal name"),
|
|
1087
|
+
mealType: MealTypeEnum.optional().describe("Meal slot (default SNACK)"),
|
|
1088
|
+
planId: zod.z.string().optional().describe("Nutrition plan ID to attribute the meal to"),
|
|
1089
|
+
calories: zod.z.number().int().optional().describe("Calories"),
|
|
1090
|
+
proteinG: zod.z.number().int().optional().describe("Protein (g)"),
|
|
1091
|
+
carbsG: zod.z.number().int().optional().describe("Carbohydrates (g)"),
|
|
1092
|
+
fatG: zod.z.number().int().optional().describe("Fat (g)"),
|
|
1093
|
+
servings: zod.z.number().optional().describe("Number of servings"),
|
|
1094
|
+
isFavorite: zod.z.boolean().optional().describe("Flag as a favorite meal"),
|
|
1095
|
+
loggedAt: zod.z.string().optional().describe("When the meal was eaten (ISO-8601 DateTime)")
|
|
1096
|
+
}
|
|
1097
|
+
},
|
|
1098
|
+
async ({
|
|
1099
|
+
name,
|
|
1100
|
+
mealType,
|
|
1101
|
+
planId,
|
|
1102
|
+
calories,
|
|
1103
|
+
proteinG,
|
|
1104
|
+
carbsG,
|
|
1105
|
+
fatG,
|
|
1106
|
+
servings,
|
|
1107
|
+
isFavorite,
|
|
1108
|
+
loggedAt
|
|
1109
|
+
}) => {
|
|
1110
|
+
try {
|
|
1111
|
+
const input = { name };
|
|
1112
|
+
if (mealType !== void 0) input.mealType = mealType;
|
|
1113
|
+
if (planId !== void 0) input.planId = planId;
|
|
1114
|
+
if (calories !== void 0) input.calories = calories;
|
|
1115
|
+
if (proteinG !== void 0) input.proteinG = proteinG;
|
|
1116
|
+
if (carbsG !== void 0) input.carbsG = carbsG;
|
|
1117
|
+
if (fatG !== void 0) input.fatG = fatG;
|
|
1118
|
+
if (servings !== void 0) input.servings = servings;
|
|
1119
|
+
if (isFavorite !== void 0) input.isFavorite = isFavorite;
|
|
1120
|
+
if (loggedAt !== void 0) input.loggedAt = loggedAt;
|
|
1121
|
+
const data = await ctx.gql.workspace.request(LOG_MEAL, { input });
|
|
1122
|
+
return jsonResult(data.mybLogMeal);
|
|
1123
|
+
} catch (error) {
|
|
1124
|
+
ctx.logger.error("mybodyshield meals_log failed", { error: String(error) });
|
|
1125
|
+
return errorResult(
|
|
1126
|
+
`Failed to log meal: ${error instanceof Error ? error.message : String(error)}`
|
|
1127
|
+
);
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
);
|
|
1131
|
+
server.registerTool(
|
|
1132
|
+
track(mcpToolName(ctx.productName, "meals_delete")),
|
|
1133
|
+
{
|
|
1134
|
+
title: "Delete Meal Log",
|
|
1135
|
+
description: "Delete a logged meal by ID. Returns true on success.",
|
|
1136
|
+
inputSchema: { id: zod.z.string().describe("Meal log ID") }
|
|
1137
|
+
},
|
|
1138
|
+
async ({ id }) => {
|
|
1139
|
+
try {
|
|
1140
|
+
const data = await ctx.gql.workspace.request(DELETE_MEAL, {
|
|
1141
|
+
id
|
|
1142
|
+
});
|
|
1143
|
+
return jsonResult({ deleted: data.mybDeleteMeal });
|
|
1144
|
+
} catch (error) {
|
|
1145
|
+
ctx.logger.error("mybodyshield meals_delete failed", { error: String(error) });
|
|
1146
|
+
return errorResult(
|
|
1147
|
+
`Failed to delete meal: ${error instanceof Error ? error.message : String(error)}`
|
|
1148
|
+
);
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
);
|
|
1152
|
+
}
|
|
1153
|
+
function registerGoalTools(server, ctx, track) {
|
|
1154
|
+
server.registerTool(
|
|
1155
|
+
track(mcpToolName(ctx.productName, "goals_get")),
|
|
1156
|
+
{
|
|
1157
|
+
title: "Get Goal",
|
|
1158
|
+
description: "Fetch a single goal by ID.",
|
|
1159
|
+
inputSchema: { id: zod.z.string().describe("Goal ID") }
|
|
1160
|
+
},
|
|
1161
|
+
async ({ id }) => {
|
|
1162
|
+
try {
|
|
1163
|
+
const data = await ctx.gql.workspace.request(GET_GOAL, { id });
|
|
1164
|
+
if (!data.mybGoal) return errorResult(`Goal ${id} not found`);
|
|
1165
|
+
return jsonResult(data.mybGoal);
|
|
1166
|
+
} catch (error) {
|
|
1167
|
+
ctx.logger.error("mybodyshield goals_get failed", { error: String(error) });
|
|
1168
|
+
return errorResult(
|
|
1169
|
+
`Failed to get goal: ${error instanceof Error ? error.message : String(error)}`
|
|
1170
|
+
);
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
);
|
|
1174
|
+
server.registerTool(
|
|
1175
|
+
track(mcpToolName(ctx.productName, "goals_list")),
|
|
1176
|
+
{
|
|
1177
|
+
title: "List Goals",
|
|
1178
|
+
description: "List goals in the bound workspace, optionally filtered by member and status. Returns a paginated connection ({ items, pageInfo }).",
|
|
1179
|
+
inputSchema: {
|
|
1180
|
+
memberId: zod.z.string().optional().describe("Filter by member (user) ID"),
|
|
1181
|
+
status: GoalStatusEnum.optional().describe("Filter by goal status"),
|
|
1182
|
+
limit: zod.z.number().int().optional().describe("Page size (default 50)"),
|
|
1183
|
+
offset: zod.z.number().int().optional().describe("Pagination offset (default 0)"),
|
|
1184
|
+
cursor: zod.z.string().optional().describe("Opaque pagination cursor")
|
|
1185
|
+
}
|
|
1186
|
+
},
|
|
1187
|
+
async ({ memberId, status, limit, offset, cursor }) => {
|
|
1188
|
+
try {
|
|
1189
|
+
const data = await ctx.gql.workspace.request(LIST_GOALS, {
|
|
1190
|
+
memberId,
|
|
1191
|
+
status,
|
|
1192
|
+
pagination: { limit: limit ?? 50, offset: offset ?? 0, cursor }
|
|
1193
|
+
});
|
|
1194
|
+
return jsonResult(data.mybListGoals);
|
|
1195
|
+
} catch (error) {
|
|
1196
|
+
ctx.logger.error("mybodyshield goals_list failed", { error: String(error) });
|
|
1197
|
+
return errorResult(
|
|
1198
|
+
`Failed to list goals: ${error instanceof Error ? error.message : String(error)}`
|
|
1199
|
+
);
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
);
|
|
1203
|
+
server.registerTool(
|
|
1204
|
+
track(mcpToolName(ctx.productName, "goals_create")),
|
|
1205
|
+
{
|
|
1206
|
+
title: "Create Goal",
|
|
1207
|
+
description: "Create a goal. `title` is required; `type` defaults to GENERAL_FITNESS. Set baseline/target values and a target date to track progress.",
|
|
1208
|
+
inputSchema: {
|
|
1209
|
+
title: zod.z.string().min(1).describe("Goal title"),
|
|
1210
|
+
memberId: zod.z.string().optional().describe("Member (user) ID the goal is for"),
|
|
1211
|
+
type: GoalTypeEnum.optional().describe("Goal type (default GENERAL_FITNESS)"),
|
|
1212
|
+
description: zod.z.string().optional().describe("Goal description"),
|
|
1213
|
+
baselineValue: zod.z.number().optional().describe("Starting value"),
|
|
1214
|
+
targetValue: zod.z.number().optional().describe("Target value"),
|
|
1215
|
+
unit: zod.z.string().optional().describe("Unit of measure (e.g. kg, reps, min)"),
|
|
1216
|
+
targetDate: zod.z.string().optional().describe("Target date (ISO-8601 DateTime)")
|
|
1217
|
+
}
|
|
1218
|
+
},
|
|
1219
|
+
async ({
|
|
1220
|
+
title,
|
|
1221
|
+
memberId,
|
|
1222
|
+
type,
|
|
1223
|
+
description,
|
|
1224
|
+
baselineValue,
|
|
1225
|
+
targetValue,
|
|
1226
|
+
unit,
|
|
1227
|
+
targetDate
|
|
1228
|
+
}) => {
|
|
1229
|
+
try {
|
|
1230
|
+
const input = { title };
|
|
1231
|
+
if (memberId !== void 0) input.memberId = memberId;
|
|
1232
|
+
if (type !== void 0) input.type = type;
|
|
1233
|
+
if (description !== void 0) input.description = description;
|
|
1234
|
+
if (baselineValue !== void 0) input.baselineValue = baselineValue;
|
|
1235
|
+
if (targetValue !== void 0) input.targetValue = targetValue;
|
|
1236
|
+
if (unit !== void 0) input.unit = unit;
|
|
1237
|
+
if (targetDate !== void 0) input.targetDate = targetDate;
|
|
1238
|
+
const data = await ctx.gql.workspace.request(CREATE_GOAL, { input });
|
|
1239
|
+
return jsonResult(data.mybCreateGoal);
|
|
1240
|
+
} catch (error) {
|
|
1241
|
+
ctx.logger.error("mybodyshield goals_create failed", { error: String(error) });
|
|
1242
|
+
return errorResult(
|
|
1243
|
+
`Failed to create goal: ${error instanceof Error ? error.message : String(error)}`
|
|
1244
|
+
);
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
);
|
|
1248
|
+
server.registerTool(
|
|
1249
|
+
track(mcpToolName(ctx.productName, "goals_update")),
|
|
1250
|
+
{
|
|
1251
|
+
title: "Update Goal",
|
|
1252
|
+
description: "Update a goal. At least one mutable field must be provided. Use `current_value` to record progress, or `goals_achieve` to mark it achieved.",
|
|
1253
|
+
inputSchema: {
|
|
1254
|
+
id: zod.z.string().describe("Goal ID"),
|
|
1255
|
+
title: zod.z.string().optional().describe("Updated title"),
|
|
1256
|
+
type: GoalTypeEnum.optional().describe("Updated goal type"),
|
|
1257
|
+
description: zod.z.string().optional().describe("Updated description"),
|
|
1258
|
+
baselineValue: zod.z.number().optional().describe("Updated baseline value"),
|
|
1259
|
+
currentValue: zod.z.number().optional().describe("Updated current/progress value"),
|
|
1260
|
+
targetValue: zod.z.number().optional().describe("Updated target value"),
|
|
1261
|
+
unit: zod.z.string().optional().describe("Updated unit of measure"),
|
|
1262
|
+
targetDate: zod.z.string().optional().describe("Updated target date (ISO-8601 DateTime)")
|
|
1263
|
+
}
|
|
1264
|
+
},
|
|
1265
|
+
async ({
|
|
1266
|
+
id,
|
|
1267
|
+
title,
|
|
1268
|
+
type,
|
|
1269
|
+
description,
|
|
1270
|
+
baselineValue,
|
|
1271
|
+
currentValue,
|
|
1272
|
+
targetValue,
|
|
1273
|
+
unit,
|
|
1274
|
+
targetDate
|
|
1275
|
+
}) => {
|
|
1276
|
+
try {
|
|
1277
|
+
const input = {};
|
|
1278
|
+
if (title !== void 0) input.title = title;
|
|
1279
|
+
if (type !== void 0) input.type = type;
|
|
1280
|
+
if (description !== void 0) input.description = description;
|
|
1281
|
+
if (baselineValue !== void 0) input.baselineValue = baselineValue;
|
|
1282
|
+
if (currentValue !== void 0) input.currentValue = currentValue;
|
|
1283
|
+
if (targetValue !== void 0) input.targetValue = targetValue;
|
|
1284
|
+
if (unit !== void 0) input.unit = unit;
|
|
1285
|
+
if (targetDate !== void 0) input.targetDate = targetDate;
|
|
1286
|
+
if (Object.keys(input).length === 0) {
|
|
1287
|
+
return errorResult(
|
|
1288
|
+
"No updates specified \u2014 provide at least one of title/type/description/baselineValue/currentValue/targetValue/unit/targetDate"
|
|
1289
|
+
);
|
|
1290
|
+
}
|
|
1291
|
+
const data = await ctx.gql.workspace.request(UPDATE_GOAL, { id, input });
|
|
1292
|
+
return jsonResult(data.mybUpdateGoal);
|
|
1293
|
+
} catch (error) {
|
|
1294
|
+
ctx.logger.error("mybodyshield goals_update failed", { error: String(error) });
|
|
1295
|
+
return errorResult(
|
|
1296
|
+
`Failed to update goal: ${error instanceof Error ? error.message : String(error)}`
|
|
1297
|
+
);
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
);
|
|
1301
|
+
server.registerTool(
|
|
1302
|
+
track(mcpToolName(ctx.productName, "goals_achieve")),
|
|
1303
|
+
{
|
|
1304
|
+
title: "Achieve Goal",
|
|
1305
|
+
description: "Mark a goal as achieved (sets status ACHIEVED + achievedAt).",
|
|
1306
|
+
inputSchema: { id: zod.z.string().describe("Goal ID") }
|
|
1307
|
+
},
|
|
1308
|
+
async ({ id }) => {
|
|
1309
|
+
try {
|
|
1310
|
+
const data = await ctx.gql.workspace.request(ACHIEVE_GOAL, { id });
|
|
1311
|
+
return jsonResult(data.mybAchieveGoal);
|
|
1312
|
+
} catch (error) {
|
|
1313
|
+
ctx.logger.error("mybodyshield goals_achieve failed", { error: String(error) });
|
|
1314
|
+
return errorResult(
|
|
1315
|
+
`Failed to achieve goal: ${error instanceof Error ? error.message : String(error)}`
|
|
1316
|
+
);
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
);
|
|
1320
|
+
}
|
|
1321
|
+
function registerMetricTools(server, ctx, track) {
|
|
1322
|
+
server.registerTool(
|
|
1323
|
+
track(mcpToolName(ctx.productName, "metrics_list")),
|
|
1324
|
+
{
|
|
1325
|
+
title: "List Body Metrics",
|
|
1326
|
+
description: "List recorded body metrics in the bound workspace, optionally filtered by member and metric type. Returns a paginated connection ({ items, pageInfo }).",
|
|
1327
|
+
inputSchema: {
|
|
1328
|
+
memberId: zod.z.string().optional().describe("Filter by member (user) ID"),
|
|
1329
|
+
type: BodyMetricTypeEnum.optional().describe("Filter by metric type"),
|
|
1330
|
+
limit: zod.z.number().int().optional().describe("Page size (default 50)"),
|
|
1331
|
+
offset: zod.z.number().int().optional().describe("Pagination offset (default 0)"),
|
|
1332
|
+
cursor: zod.z.string().optional().describe("Opaque pagination cursor")
|
|
1333
|
+
}
|
|
1334
|
+
},
|
|
1335
|
+
async ({ memberId, type, limit, offset, cursor }) => {
|
|
1336
|
+
try {
|
|
1337
|
+
const data = await ctx.gql.workspace.request(LIST_BODY_METRICS, {
|
|
1338
|
+
memberId,
|
|
1339
|
+
type,
|
|
1340
|
+
pagination: { limit: limit ?? 50, offset: offset ?? 0, cursor }
|
|
1341
|
+
});
|
|
1342
|
+
return jsonResult(data.mybListBodyMetrics);
|
|
1343
|
+
} catch (error) {
|
|
1344
|
+
ctx.logger.error("mybodyshield metrics_list failed", { error: String(error) });
|
|
1345
|
+
return errorResult(
|
|
1346
|
+
`Failed to list body metrics: ${error instanceof Error ? error.message : String(error)}`
|
|
1347
|
+
);
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
);
|
|
1351
|
+
server.registerTool(
|
|
1352
|
+
track(mcpToolName(ctx.productName, "metrics_record")),
|
|
1353
|
+
{
|
|
1354
|
+
title: "Record Body Metric",
|
|
1355
|
+
description: "Record a body measurement. `type` and `value` are required; attach a progress photo via `photoFileId` (a wspace-files-svc File ID).",
|
|
1356
|
+
inputSchema: {
|
|
1357
|
+
type: BodyMetricTypeEnum.describe("Metric type"),
|
|
1358
|
+
value: zod.z.number().describe("Measured value"),
|
|
1359
|
+
unit: zod.z.string().optional().describe("Unit of measure (e.g. kg, cm, %, bpm)"),
|
|
1360
|
+
memberId: zod.z.string().optional().describe("Member (user) ID (default: calling actor)"),
|
|
1361
|
+
measuredAt: zod.z.string().optional().describe("When measured (ISO-8601 DateTime; default: now)"),
|
|
1362
|
+
notes: zod.z.string().optional().describe("Notes"),
|
|
1363
|
+
photoFileId: zod.z.string().optional().describe("wspace-files-svc File ID for a progress photo")
|
|
1364
|
+
}
|
|
1365
|
+
},
|
|
1366
|
+
async ({ type, value, unit, memberId, measuredAt, notes, photoFileId }) => {
|
|
1367
|
+
try {
|
|
1368
|
+
const input = { type, value };
|
|
1369
|
+
if (unit !== void 0) input.unit = unit;
|
|
1370
|
+
if (memberId !== void 0) input.memberId = memberId;
|
|
1371
|
+
if (measuredAt !== void 0) input.measuredAt = measuredAt;
|
|
1372
|
+
if (notes !== void 0) input.notes = notes;
|
|
1373
|
+
if (photoFileId !== void 0) input.photoFileId = photoFileId;
|
|
1374
|
+
const data = await ctx.gql.workspace.request(RECORD_BODY_METRIC, { input });
|
|
1375
|
+
return jsonResult(data.mybRecordBodyMetric);
|
|
1376
|
+
} catch (error) {
|
|
1377
|
+
ctx.logger.error("mybodyshield metrics_record failed", { error: String(error) });
|
|
1378
|
+
return errorResult(
|
|
1379
|
+
`Failed to record body metric: ${error instanceof Error ? error.message : String(error)}`
|
|
1380
|
+
);
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
);
|
|
1384
|
+
}
|
|
1385
|
+
function registerRecoveryTools(server, ctx, track) {
|
|
1386
|
+
server.registerTool(
|
|
1387
|
+
track(mcpToolName(ctx.productName, "recovery_list")),
|
|
1388
|
+
{
|
|
1389
|
+
title: "List Recovery Logs",
|
|
1390
|
+
description: "List recovery/wellbeing logs in the bound workspace, optionally filtered by member and metric. Returns a plain array.",
|
|
1391
|
+
inputSchema: {
|
|
1392
|
+
memberId: zod.z.string().optional().describe("Filter by member (user) ID"),
|
|
1393
|
+
metric: RecoveryMetricEnum.optional().describe("Filter by recovery metric")
|
|
1394
|
+
}
|
|
1395
|
+
},
|
|
1396
|
+
async ({ memberId, metric }) => {
|
|
1397
|
+
try {
|
|
1398
|
+
const data = await ctx.gql.workspace.request(LIST_RECOVERY_LOGS, { memberId, metric });
|
|
1399
|
+
return jsonResult(data.mybListRecoveryLogs);
|
|
1400
|
+
} catch (error) {
|
|
1401
|
+
ctx.logger.error("mybodyshield recovery_list failed", { error: String(error) });
|
|
1402
|
+
return errorResult(
|
|
1403
|
+
`Failed to list recovery logs: ${error instanceof Error ? error.message : String(error)}`
|
|
1404
|
+
);
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
);
|
|
1408
|
+
server.registerTool(
|
|
1409
|
+
track(mcpToolName(ctx.productName, "recovery_log")),
|
|
1410
|
+
{
|
|
1411
|
+
title: "Log Recovery Signal",
|
|
1412
|
+
description: "Log a recovery/wellbeing signal (sleep, soreness, readiness, mood, stress). `metric` defaults to READINESS. Record a 0\u2013100 `score` and/or `sleepHours`.",
|
|
1413
|
+
inputSchema: {
|
|
1414
|
+
metric: RecoveryMetricEnum.optional().describe("Recovery metric (default READINESS)"),
|
|
1415
|
+
score: zod.z.number().int().optional().describe("Score (typically 0\u2013100)"),
|
|
1416
|
+
sleepHours: zod.z.number().optional().describe("Hours slept"),
|
|
1417
|
+
memberId: zod.z.string().optional().describe("Member (user) ID (default: calling actor)"),
|
|
1418
|
+
loggedAt: zod.z.string().optional().describe("When logged (ISO-8601 DateTime; default: now)"),
|
|
1419
|
+
notes: zod.z.string().optional().describe("Notes")
|
|
1420
|
+
}
|
|
1421
|
+
},
|
|
1422
|
+
async ({ metric, score, sleepHours, memberId, loggedAt, notes }) => {
|
|
1423
|
+
try {
|
|
1424
|
+
const input = {};
|
|
1425
|
+
if (metric !== void 0) input.metric = metric;
|
|
1426
|
+
if (score !== void 0) input.score = score;
|
|
1427
|
+
if (sleepHours !== void 0) input.sleepHours = sleepHours;
|
|
1428
|
+
if (memberId !== void 0) input.memberId = memberId;
|
|
1429
|
+
if (loggedAt !== void 0) input.loggedAt = loggedAt;
|
|
1430
|
+
if (notes !== void 0) input.notes = notes;
|
|
1431
|
+
const data = await ctx.gql.workspace.request(LOG_RECOVERY, { input });
|
|
1432
|
+
return jsonResult(data.mybLogRecovery);
|
|
1433
|
+
} catch (error) {
|
|
1434
|
+
ctx.logger.error("mybodyshield recovery_log failed", { error: String(error) });
|
|
1435
|
+
return errorResult(
|
|
1436
|
+
`Failed to log recovery: ${error instanceof Error ? error.message : String(error)}`
|
|
1437
|
+
);
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
);
|
|
1441
|
+
}
|
|
1442
|
+
function registerCoachingTools(server, ctx, track) {
|
|
1443
|
+
server.registerTool(
|
|
1444
|
+
track(mcpToolName(ctx.productName, "coaching_list")),
|
|
1445
|
+
{
|
|
1446
|
+
title: "List Coaching Relationships",
|
|
1447
|
+
description: "List coach\u2194member coaching relationships in the bound workspace, optionally filtered by member and status. Returns a plain array.",
|
|
1448
|
+
inputSchema: {
|
|
1449
|
+
memberId: zod.z.string().optional().describe("Filter by member (user) ID"),
|
|
1450
|
+
status: CoachingStatusEnum.optional().describe("Filter by coaching status")
|
|
1451
|
+
}
|
|
1452
|
+
},
|
|
1453
|
+
async ({ memberId, status }) => {
|
|
1454
|
+
try {
|
|
1455
|
+
const data = await ctx.gql.workspace.request(LIST_COACHING, { memberId, status });
|
|
1456
|
+
return jsonResult(data.mybListCoaching);
|
|
1457
|
+
} catch (error) {
|
|
1458
|
+
ctx.logger.error("mybodyshield coaching_list failed", { error: String(error) });
|
|
1459
|
+
return errorResult(
|
|
1460
|
+
`Failed to list coaching relationships: ${error instanceof Error ? error.message : String(error)}`
|
|
1461
|
+
);
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
);
|
|
1465
|
+
server.registerTool(
|
|
1466
|
+
track(mcpToolName(ctx.productName, "coaching_create")),
|
|
1467
|
+
{
|
|
1468
|
+
title: "Create Coaching Relationship",
|
|
1469
|
+
description: "Create a coaching relationship between a coach and a member (starts PENDING). Use the share flags to control what the coach can see.",
|
|
1470
|
+
inputSchema: {
|
|
1471
|
+
coachId: zod.z.string().describe("Coach (user) ID"),
|
|
1472
|
+
memberId: zod.z.string().describe("Member (user) ID"),
|
|
1473
|
+
specialty: zod.z.string().optional().describe("Coach's specialty"),
|
|
1474
|
+
shareWorkouts: zod.z.boolean().optional().describe("Share workout data with the coach"),
|
|
1475
|
+
shareNutrition: zod.z.boolean().optional().describe("Share nutrition data with the coach"),
|
|
1476
|
+
shareMetrics: zod.z.boolean().optional().describe("Share body-metric data with the coach")
|
|
1477
|
+
}
|
|
1478
|
+
},
|
|
1479
|
+
async ({ coachId, memberId, specialty, shareWorkouts, shareNutrition, shareMetrics }) => {
|
|
1480
|
+
try {
|
|
1481
|
+
const input = { coachId, memberId };
|
|
1482
|
+
if (specialty !== void 0) input.specialty = specialty;
|
|
1483
|
+
if (shareWorkouts !== void 0) input.shareWorkouts = shareWorkouts;
|
|
1484
|
+
if (shareNutrition !== void 0) input.shareNutrition = shareNutrition;
|
|
1485
|
+
if (shareMetrics !== void 0) input.shareMetrics = shareMetrics;
|
|
1486
|
+
const data = await ctx.gql.workspace.request(CREATE_COACHING, { input });
|
|
1487
|
+
return jsonResult(data.mybCreateCoaching);
|
|
1488
|
+
} catch (error) {
|
|
1489
|
+
ctx.logger.error("mybodyshield coaching_create failed", { error: String(error) });
|
|
1490
|
+
return errorResult(
|
|
1491
|
+
`Failed to create coaching relationship: ${error instanceof Error ? error.message : String(error)}`
|
|
1492
|
+
);
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1495
|
+
);
|
|
1496
|
+
server.registerTool(
|
|
1497
|
+
track(mcpToolName(ctx.productName, "coaching_transition")),
|
|
1498
|
+
{
|
|
1499
|
+
title: "Transition Coaching Relationship",
|
|
1500
|
+
description: "Move a coaching relationship through its lifecycle: PENDING \u2192 ACTIVE \u2192 PAUSED / ENDED.",
|
|
1501
|
+
inputSchema: {
|
|
1502
|
+
id: zod.z.string().describe("Coaching relationship ID"),
|
|
1503
|
+
status: CoachingStatusEnum.describe("Target coaching status")
|
|
1504
|
+
}
|
|
1505
|
+
},
|
|
1506
|
+
async ({ id, status }) => {
|
|
1507
|
+
try {
|
|
1508
|
+
const data = await ctx.gql.workspace.request(TRANSITION_COACHING, { id, status });
|
|
1509
|
+
return jsonResult(data.mybTransitionCoaching);
|
|
1510
|
+
} catch (error) {
|
|
1511
|
+
ctx.logger.error("mybodyshield coaching_transition failed", { error: String(error) });
|
|
1512
|
+
return errorResult(
|
|
1513
|
+
`Failed to transition coaching relationship: ${error instanceof Error ? error.message : String(error)}`
|
|
1514
|
+
);
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
);
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1520
|
+
// src/modules/mybodyshield/tools/health.ts
|
|
1521
|
+
function registerHealthTools(server, ctx, track) {
|
|
1522
|
+
server.registerTool(
|
|
1523
|
+
track(mcpToolName(ctx.productName, "health")),
|
|
1524
|
+
{
|
|
1525
|
+
title: "Mybodyshield Health Check",
|
|
1526
|
+
description: "Public liveness/identity probe for the Mybodyshield subgraph (mybHealth). Requires no authentication.",
|
|
1527
|
+
inputSchema: {}
|
|
1528
|
+
},
|
|
1529
|
+
async () => {
|
|
1530
|
+
try {
|
|
1531
|
+
const data = await ctx.gql.workspace.request(HEALTH, {});
|
|
1532
|
+
return jsonResult({ ok: true, status: data.mybHealth });
|
|
1533
|
+
} catch (error) {
|
|
1534
|
+
ctx.logger.error("mybodyshield health failed", { error: String(error) });
|
|
1535
|
+
return errorResult(
|
|
1536
|
+
`Mybodyshield health check failed: ${error instanceof Error ? error.message : String(error)}`
|
|
1537
|
+
);
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
);
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1543
|
+
// src/modules/mybodyshield/resources.ts
|
|
1544
|
+
function registerMybodyshieldResources(server, ctx, track) {
|
|
1545
|
+
const schemaUri = track(mcpResourceUri(ctx.productName, "schema"));
|
|
1546
|
+
const programsUri = track(mcpResourceUri(ctx.productName, "programs"));
|
|
1547
|
+
const goalsUri = track(mcpResourceUri(ctx.productName, "goals"));
|
|
1548
|
+
const nutritionUri = track(mcpResourceUri(ctx.productName, "nutrition"));
|
|
1549
|
+
server.registerResource(
|
|
1550
|
+
"mybodyshield-schema",
|
|
1551
|
+
schemaUri,
|
|
1552
|
+
{
|
|
1553
|
+
title: "Mybodyshield Domain Schema",
|
|
1554
|
+
description: "Overview of the Mybodyshield health & fitness domain \u2014 entities, enums, and the per-entity tool surface.",
|
|
1555
|
+
mimeType: "text/plain"
|
|
1556
|
+
},
|
|
1557
|
+
async () => {
|
|
1558
|
+
ctx.logger.debug(`Resource ${schemaUri} requested`);
|
|
1559
|
+
const schema = `# Mybodyshield Domain (Overview)
|
|
1560
|
+
|
|
1561
|
+
Health & fitness platform \u2014 system of record for training, nutrition, wellbeing, and coaching.
|
|
1562
|
+
|
|
1563
|
+
## Entities
|
|
1564
|
+
- MybProgram \u2014 a training program (has workouts)
|
|
1565
|
+
- MybWorkout \u2014 a workout within a program (has exercises)
|
|
1566
|
+
- MybExercise \u2014 an exercise within a workout (demo File via federation)
|
|
1567
|
+
- MybWorkoutSession \u2014 a logged/scheduled session (has sets)
|
|
1568
|
+
- MybSessionSet \u2014 a single set logged in a session
|
|
1569
|
+
- MybNutritionPlan \u2014 a nutrition plan with macro targets (has meals)
|
|
1570
|
+
- MybMealLog \u2014 a logged meal with macros
|
|
1571
|
+
- MybGoal \u2014 a tracked fitness goal
|
|
1572
|
+
- MybBodyMetric \u2014 a body measurement (progress photo File via federation)
|
|
1573
|
+
- MybRecoveryLog \u2014 a recovery/wellbeing signal (sleep, soreness, readiness, mood, stress)
|
|
1574
|
+
- MybCoachingRelationship \u2014 a coach\u2194member relationship with share scopes
|
|
1575
|
+
|
|
1576
|
+
## Enums
|
|
1577
|
+
- GoalType: ENDURANCE, GENERAL_FITNESS, MOBILITY, MUSCLE_GAIN, STRENGTH, WEIGHT_LOSS
|
|
1578
|
+
- GoalStatus: ABANDONED, ACHIEVED, ACTIVE, PAUSED
|
|
1579
|
+
- FitnessLevel: ADVANCED, BEGINNER, ELITE, INTERMEDIATE
|
|
1580
|
+
- ProgramPhase: BUILD, DELOAD, MAINTENANCE, PEAK
|
|
1581
|
+
- ProgramStatus: ACTIVE, ARCHIVED, COMPLETED, DRAFT
|
|
1582
|
+
- WorkoutType: CARDIO, HIIT, MOBILITY, RECOVERY, STRENGTH, YOGA
|
|
1583
|
+
- MuscleGroup: ARMS, BACK, CHEST, CORE, FULL_BODY, LEGS, SHOULDERS
|
|
1584
|
+
- SessionStatus: COMPLETED, IN_PROGRESS, SCHEDULED, SKIPPED
|
|
1585
|
+
- MealType: BREAKFAST, DINNER, LUNCH, SNACK
|
|
1586
|
+
- BodyMetricType: ARM, BODY_FAT, CHEST, HIPS, RESTING_HEART_RATE, THIGH, WAIST, WEIGHT
|
|
1587
|
+
- RecoveryMetric: MOOD, READINESS, SLEEP, SORENESS, STRESS
|
|
1588
|
+
- CoachingStatus: ACTIVE, ENDED, PAUSED, PENDING
|
|
1589
|
+
|
|
1590
|
+
## Tool groups
|
|
1591
|
+
programs, workouts (+exercises), sessions (+sets), nutrition (+daily_macros),
|
|
1592
|
+
meals, goals, metrics, recovery, coaching, health.
|
|
1593
|
+
All run against the WORKSPACE gateway (workspace-scoped) except the public health probe.
|
|
1594
|
+
`;
|
|
1595
|
+
return {
|
|
1596
|
+
contents: [{ uri: schemaUri, text: schema, mimeType: "text/plain" }]
|
|
1597
|
+
};
|
|
1598
|
+
}
|
|
1599
|
+
);
|
|
1600
|
+
server.registerResource(
|
|
1601
|
+
"mybodyshield-programs",
|
|
1602
|
+
programsUri,
|
|
1603
|
+
{
|
|
1604
|
+
title: "Mybodyshield Programs",
|
|
1605
|
+
description: "The first 50 training programs in the bound workspace.",
|
|
1606
|
+
mimeType: "application/json"
|
|
1607
|
+
},
|
|
1608
|
+
async () => {
|
|
1609
|
+
ctx.logger.debug(`Resource ${programsUri} requested`);
|
|
1610
|
+
try {
|
|
1611
|
+
const data = await ctx.gql.workspace.request(LIST_PROGRAMS, {
|
|
1612
|
+
pagination: { limit: 50, offset: 0 }
|
|
1613
|
+
});
|
|
1614
|
+
return {
|
|
1615
|
+
contents: [
|
|
1616
|
+
{
|
|
1617
|
+
uri: programsUri,
|
|
1618
|
+
text: JSON.stringify(data.mybListPrograms, null, 2),
|
|
1619
|
+
mimeType: "application/json"
|
|
1620
|
+
}
|
|
1621
|
+
]
|
|
1622
|
+
};
|
|
1623
|
+
} catch (error) {
|
|
1624
|
+
return {
|
|
1625
|
+
contents: [
|
|
1626
|
+
{
|
|
1627
|
+
uri: programsUri,
|
|
1628
|
+
text: JSON.stringify({
|
|
1629
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1630
|
+
}),
|
|
1631
|
+
mimeType: "application/json"
|
|
1632
|
+
}
|
|
1633
|
+
]
|
|
1634
|
+
};
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1637
|
+
);
|
|
1638
|
+
server.registerResource(
|
|
1639
|
+
"mybodyshield-goals",
|
|
1640
|
+
goalsUri,
|
|
1641
|
+
{
|
|
1642
|
+
title: "Mybodyshield Goals",
|
|
1643
|
+
description: "The first 50 goals in the bound workspace.",
|
|
1644
|
+
mimeType: "application/json"
|
|
1645
|
+
},
|
|
1646
|
+
async () => {
|
|
1647
|
+
ctx.logger.debug(`Resource ${goalsUri} requested`);
|
|
1648
|
+
try {
|
|
1649
|
+
const data = await ctx.gql.workspace.request(LIST_GOALS, {
|
|
1650
|
+
pagination: { limit: 50, offset: 0 }
|
|
1651
|
+
});
|
|
1652
|
+
return {
|
|
1653
|
+
contents: [
|
|
1654
|
+
{
|
|
1655
|
+
uri: goalsUri,
|
|
1656
|
+
text: JSON.stringify(data.mybListGoals, null, 2),
|
|
1657
|
+
mimeType: "application/json"
|
|
1658
|
+
}
|
|
1659
|
+
]
|
|
1660
|
+
};
|
|
1661
|
+
} catch (error) {
|
|
1662
|
+
return {
|
|
1663
|
+
contents: [
|
|
1664
|
+
{
|
|
1665
|
+
uri: goalsUri,
|
|
1666
|
+
text: JSON.stringify({
|
|
1667
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1668
|
+
}),
|
|
1669
|
+
mimeType: "application/json"
|
|
1670
|
+
}
|
|
1671
|
+
]
|
|
1672
|
+
};
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1675
|
+
);
|
|
1676
|
+
server.registerResource(
|
|
1677
|
+
"mybodyshield-nutrition",
|
|
1678
|
+
nutritionUri,
|
|
1679
|
+
{
|
|
1680
|
+
title: "Mybodyshield Active Nutrition Plan",
|
|
1681
|
+
description: "The calling actor's active nutrition plan (with logged meals), if any.",
|
|
1682
|
+
mimeType: "application/json"
|
|
1683
|
+
},
|
|
1684
|
+
async () => {
|
|
1685
|
+
ctx.logger.debug(`Resource ${nutritionUri} requested`);
|
|
1686
|
+
try {
|
|
1687
|
+
const data = await ctx.gql.workspace.request(
|
|
1688
|
+
ACTIVE_NUTRITION_PLAN,
|
|
1689
|
+
{}
|
|
1690
|
+
);
|
|
1691
|
+
return {
|
|
1692
|
+
contents: [
|
|
1693
|
+
{
|
|
1694
|
+
uri: nutritionUri,
|
|
1695
|
+
text: JSON.stringify(data.mybActiveNutritionPlan, null, 2),
|
|
1696
|
+
mimeType: "application/json"
|
|
1697
|
+
}
|
|
1698
|
+
]
|
|
1699
|
+
};
|
|
1700
|
+
} catch (error) {
|
|
1701
|
+
return {
|
|
1702
|
+
contents: [
|
|
1703
|
+
{
|
|
1704
|
+
uri: nutritionUri,
|
|
1705
|
+
text: JSON.stringify({
|
|
1706
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1707
|
+
}),
|
|
1708
|
+
mimeType: "application/json"
|
|
1709
|
+
}
|
|
1710
|
+
]
|
|
1711
|
+
};
|
|
1712
|
+
}
|
|
1713
|
+
}
|
|
1714
|
+
);
|
|
1715
|
+
}
|
|
1716
|
+
function registerMybodyshieldPrompts(server, ctx, track) {
|
|
1717
|
+
const p = ctx.productName;
|
|
1718
|
+
const designName = track(mcpPromptName(p, "program-design"));
|
|
1719
|
+
const reviewName = track(mcpPromptName(p, "coaching-review"));
|
|
1720
|
+
server.registerPrompt(
|
|
1721
|
+
designName,
|
|
1722
|
+
{
|
|
1723
|
+
title: "Program Design",
|
|
1724
|
+
description: "Guide for designing a Mybodyshield training program end to end: create the program, add workouts and exercises, optionally pair a nutrition plan, and activate.",
|
|
1725
|
+
argsSchema: {
|
|
1726
|
+
memberId: zod.z.string().optional().describe("Member the program is for"),
|
|
1727
|
+
goalType: zod.z.string().optional().describe("Primary goal (e.g. STRENGTH, WEIGHT_LOSS)")
|
|
1728
|
+
}
|
|
1729
|
+
},
|
|
1730
|
+
({ memberId, goalType }) => {
|
|
1731
|
+
ctx.logger.debug(`Prompt ${designName} called`, { memberId, goalType });
|
|
1732
|
+
const memberNote = memberId ? `Design it for member "${memberId}".` : "Ask the user which member the program is for.";
|
|
1733
|
+
const goalNote = goalType ? `Use goalType ${goalType}.` : "Help the user pick a GoalType.";
|
|
1734
|
+
return {
|
|
1735
|
+
messages: [
|
|
1736
|
+
{
|
|
1737
|
+
role: "user",
|
|
1738
|
+
content: {
|
|
1739
|
+
type: "text",
|
|
1740
|
+
text: `Help me design a new Mybodyshield training program.
|
|
1741
|
+
|
|
1742
|
+
1. **Create Program**: Use ${p}_programs_create. ${memberNote} ${goalNote} Set level, phase, daysPerWeek, and weeks.
|
|
1743
|
+
2. **Add Workouts**: Use ${p}_workouts_add for each session in the week (set type, dayOfWeek, weekNumber, durationMin).
|
|
1744
|
+
3. **Add Exercises**: Use ${p}_exercises_add for each exercise in a workout (muscleGroup, sets, reps, restSeconds, targetWeight).
|
|
1745
|
+
4. **(Optional) Nutrition**: Use ${p}_nutrition_create to pair a macro plan (calorieTarget, proteinTarget, carbsTarget, fatTarget) and ${p}_nutrition_activate to make it active.
|
|
1746
|
+
5. **Verify**: Use ${p}_programs_get to review the full program tree.
|
|
1747
|
+
6. **Activate**: Use ${p}_programs_transition with status=ACTIVE when ready.
|
|
1748
|
+
|
|
1749
|
+
Show results at each step and confirm before activation.`
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1752
|
+
]
|
|
1753
|
+
};
|
|
1754
|
+
}
|
|
1755
|
+
);
|
|
1756
|
+
server.registerPrompt(
|
|
1757
|
+
reviewName,
|
|
1758
|
+
{
|
|
1759
|
+
title: "Coaching Review",
|
|
1760
|
+
description: "Periodic member-review workflow for a coach: review recent sessions, body metrics, recovery signals, daily macros, and goal progress, then adjust the program.",
|
|
1761
|
+
argsSchema: {
|
|
1762
|
+
memberId: zod.z.string().optional().describe("Member to review")
|
|
1763
|
+
}
|
|
1764
|
+
},
|
|
1765
|
+
({ memberId }) => {
|
|
1766
|
+
ctx.logger.debug(`Prompt ${reviewName} called`, { memberId });
|
|
1767
|
+
const memberNote = memberId ? `Review member "${memberId}".` : `First list coaching relationships with ${p}_coaching_list and pick a member.`;
|
|
1768
|
+
return {
|
|
1769
|
+
messages: [
|
|
1770
|
+
{
|
|
1771
|
+
role: "user",
|
|
1772
|
+
content: {
|
|
1773
|
+
type: "text",
|
|
1774
|
+
text: `Run a Mybodyshield coaching review. ${memberNote}
|
|
1775
|
+
|
|
1776
|
+
1. **Training**: Use ${p}_sessions_list (filter by program/status) to see recent workout sessions and adherence.
|
|
1777
|
+
2. **Body Metrics**: Use ${p}_metrics_list (filter by memberId) to chart weight / body-fat / measurement trends.
|
|
1778
|
+
3. **Recovery**: Use ${p}_recovery_list (filter by memberId) to check sleep, soreness, readiness, mood, and stress.
|
|
1779
|
+
4. **Nutrition**: Use ${p}_nutrition_daily_macros for the member to compare consumed vs target macros, and ${p}_nutrition_active for the current plan.
|
|
1780
|
+
5. **Goals**: Use ${p}_goals_list (filter by memberId) \u2014 mark hit goals with ${p}_goals_achieve and update progress with ${p}_goals_update.
|
|
1781
|
+
6. **Adjust**: Use ${p}_programs_update / ${p}_nutrition_update to adapt the plan based on findings.
|
|
1782
|
+
|
|
1783
|
+
Summarize what needs attention after each step.`
|
|
1784
|
+
}
|
|
1785
|
+
}
|
|
1786
|
+
]
|
|
1787
|
+
};
|
|
1788
|
+
}
|
|
1789
|
+
);
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1792
|
+
// src/modules/mybodyshield/index.ts
|
|
1793
|
+
var mybodyshieldModule = {
|
|
1794
|
+
id: "mybodyshield",
|
|
1795
|
+
version: "0.0.1",
|
|
1796
|
+
displayName: "Mybodyshield",
|
|
1797
|
+
dependsOn: ["auth"],
|
|
1798
|
+
capabilities: { tools: {}, resources: {}, prompts: {} },
|
|
1799
|
+
register(server, ctx) {
|
|
1800
|
+
const tools = [];
|
|
1801
|
+
const resources = [];
|
|
1802
|
+
const prompts = [];
|
|
1803
|
+
const trackTool = (n) => {
|
|
1804
|
+
tools.push(n);
|
|
1805
|
+
return n;
|
|
1806
|
+
};
|
|
1807
|
+
const trackResource = (uri) => {
|
|
1808
|
+
resources.push(uri);
|
|
1809
|
+
return uri;
|
|
1810
|
+
};
|
|
1811
|
+
const trackPrompt = (n) => {
|
|
1812
|
+
prompts.push(n);
|
|
1813
|
+
return n;
|
|
1814
|
+
};
|
|
1815
|
+
registerProgramTools(server, ctx, trackTool);
|
|
1816
|
+
registerWorkoutTools(server, ctx, trackTool);
|
|
1817
|
+
registerSessionTools(server, ctx, trackTool);
|
|
1818
|
+
registerNutritionTools(server, ctx, trackTool);
|
|
1819
|
+
registerMealTools(server, ctx, trackTool);
|
|
1820
|
+
registerGoalTools(server, ctx, trackTool);
|
|
1821
|
+
registerMetricTools(server, ctx, trackTool);
|
|
1822
|
+
registerRecoveryTools(server, ctx, trackTool);
|
|
1823
|
+
registerCoachingTools(server, ctx, trackTool);
|
|
1824
|
+
registerHealthTools(server, ctx, trackTool);
|
|
1825
|
+
registerMybodyshieldResources(server, ctx, trackResource);
|
|
1826
|
+
registerMybodyshieldPrompts(server, ctx, trackPrompt);
|
|
1827
|
+
return { tools, resources, prompts };
|
|
1828
|
+
}
|
|
1829
|
+
};
|
|
1830
|
+
var mybodyshield_default = mybodyshieldModule;
|
|
1831
|
+
|
|
1832
|
+
module.exports = mybodyshield_default;
|
|
1833
|
+
//# sourceMappingURL=index.cjs.map
|
|
1834
|
+
//# sourceMappingURL=index.cjs.map
|