@burdenoff/mcp-libs 2026.627.2 → 2026.627.4
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/artistrybase/index.cjs +2112 -0
- package/dist/modules/artistrybase/index.cjs.map +1 -0
- package/dist/modules/artistrybase/index.d.cts +8 -0
- package/dist/modules/artistrybase/index.d.ts +8 -0
- package/dist/modules/artistrybase/index.js +2051 -0
- package/dist/modules/artistrybase/index.js.map +1 -0
- package/dist/modules/brainyrich/index.cjs +2148 -0
- package/dist/modules/brainyrich/index.cjs.map +1 -0
- package/dist/modules/brainyrich/index.d.cts +8 -0
- package/dist/modules/brainyrich/index.d.ts +8 -0
- package/dist/modules/brainyrich/index.js +2087 -0
- package/dist/modules/brainyrich/index.js.map +1 -0
- package/dist/modules/buildmyiq/index.cjs +1562 -0
- package/dist/modules/buildmyiq/index.cjs.map +1 -0
- package/dist/modules/buildmyiq/index.d.cts +8 -0
- package/dist/modules/buildmyiq/index.d.ts +8 -0
- package/dist/modules/buildmyiq/index.js +1501 -0
- package/dist/modules/buildmyiq/index.js.map +1 -0
- package/dist/modules/collabkin/index.cjs +1577 -0
- package/dist/modules/collabkin/index.cjs.map +1 -0
- package/dist/modules/collabkin/index.d.cts +8 -0
- package/dist/modules/collabkin/index.d.ts +8 -0
- package/dist/modules/collabkin/index.js +1516 -0
- package/dist/modules/collabkin/index.js.map +1 -0
- package/dist/modules/comradecircle/index.cjs +1817 -0
- package/dist/modules/comradecircle/index.cjs.map +1 -0
- package/dist/modules/comradecircle/index.d.cts +8 -0
- package/dist/modules/comradecircle/index.d.ts +8 -0
- package/dist/modules/comradecircle/index.js +1756 -0
- package/dist/modules/comradecircle/index.js.map +1 -0
- package/dist/modules/cosmicintersection/index.cjs +1984 -0
- package/dist/modules/cosmicintersection/index.cjs.map +1 -0
- package/dist/modules/cosmicintersection/index.d.cts +8 -0
- package/dist/modules/cosmicintersection/index.d.ts +8 -0
- package/dist/modules/cosmicintersection/index.js +1923 -0
- package/dist/modules/cosmicintersection/index.js.map +1 -0
- package/dist/modules/ecoimpacthub/index.cjs +2042 -0
- package/dist/modules/ecoimpacthub/index.cjs.map +1 -0
- package/dist/modules/ecoimpacthub/index.d.cts +8 -0
- package/dist/modules/ecoimpacthub/index.d.ts +8 -0
- package/dist/modules/ecoimpacthub/index.js +1981 -0
- package/dist/modules/ecoimpacthub/index.js.map +1 -0
- package/dist/modules/govcitizenhub/index.cjs +1686 -0
- package/dist/modules/govcitizenhub/index.cjs.map +1 -0
- package/dist/modules/govcitizenhub/index.d.cts +8 -0
- package/dist/modules/govcitizenhub/index.d.ts +8 -0
- package/dist/modules/govcitizenhub/index.js +1625 -0
- package/dist/modules/govcitizenhub/index.js.map +1 -0
- package/dist/modules/intelwatchtower/index.cjs +1919 -0
- package/dist/modules/intelwatchtower/index.cjs.map +1 -0
- package/dist/modules/intelwatchtower/index.d.cts +8 -0
- package/dist/modules/intelwatchtower/index.d.ts +8 -0
- package/dist/modules/intelwatchtower/index.js +1858 -0
- package/dist/modules/intelwatchtower/index.js.map +1 -0
- package/dist/modules/manufacturedops/index.cjs +1884 -0
- package/dist/modules/manufacturedops/index.cjs.map +1 -0
- package/dist/modules/manufacturedops/index.d.cts +8 -0
- package/dist/modules/manufacturedops/index.d.ts +8 -0
- package/dist/modules/manufacturedops/index.js +1823 -0
- package/dist/modules/manufacturedops/index.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,1501 @@
|
|
|
1
|
+
import { mcpToolName, errorResult, jsonResult, mcpResourceUri, mcpPromptName } from '../../chunk-H2OYWLOA.js';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
|
|
4
|
+
// src/modules/buildmyiq/operations.ts
|
|
5
|
+
var PAGE_INFO_FIELDS = `cursor hasMore`;
|
|
6
|
+
var COURSE_FIELDS = `
|
|
7
|
+
id title slug summary description level status language tags estimatedHours
|
|
8
|
+
isPublic instructorId skillId publishedAt workspaceId createdAt updatedAt
|
|
9
|
+
cover { id }
|
|
10
|
+
`;
|
|
11
|
+
var MODULE_FIELDS = `
|
|
12
|
+
id courseId title description position workspaceId createdAt updatedAt
|
|
13
|
+
`;
|
|
14
|
+
var LESSON_FIELDS = `
|
|
15
|
+
id courseId moduleId title type content durationMin isPreview position
|
|
16
|
+
workspaceId createdAt updatedAt media { id }
|
|
17
|
+
`;
|
|
18
|
+
var SKILL_FIELDS = `
|
|
19
|
+
id name description category isActive workspaceId createdAt updatedAt
|
|
20
|
+
`;
|
|
21
|
+
var ENROLLMENT_FIELDS = `
|
|
22
|
+
id courseId learnerId cohortId status progressPct enrolledAt startedAt
|
|
23
|
+
completedAt lastActiveAt expiresAt workspaceId createdAt updatedAt
|
|
24
|
+
`;
|
|
25
|
+
var LESSON_PROGRESS_FIELDS = `
|
|
26
|
+
id enrollmentId lessonId status secondsSpent completedAt workspaceId
|
|
27
|
+
createdAt updatedAt
|
|
28
|
+
`;
|
|
29
|
+
var COHORT_FIELDS = `
|
|
30
|
+
id courseId name status capacity facilitatorId startDate endDate
|
|
31
|
+
workspaceId createdAt updatedAt
|
|
32
|
+
`;
|
|
33
|
+
var CERTIFICATE_FIELDS = `
|
|
34
|
+
id courseId enrollmentId learnerId serial status finalScore issuedAt
|
|
35
|
+
expiresAt revokedAt workspaceId createdAt updatedAt file { id }
|
|
36
|
+
`;
|
|
37
|
+
var ASSESSMENT_FIELDS = `
|
|
38
|
+
id courseId title description type status passingScore maxAttempts
|
|
39
|
+
timeLimitMin isGraded workspaceId createdAt updatedAt
|
|
40
|
+
`;
|
|
41
|
+
var QUESTION_FIELDS = `
|
|
42
|
+
id assessmentId type prompt options points position explanation
|
|
43
|
+
workspaceId createdAt updatedAt
|
|
44
|
+
`;
|
|
45
|
+
var SUBMISSION_FIELDS = `
|
|
46
|
+
id assessmentId enrollmentId learnerId attempt status answers score passed
|
|
47
|
+
feedback gradedById gradedAt submittedAt workspaceId createdAt updatedAt
|
|
48
|
+
`;
|
|
49
|
+
var GET_COURSE = `
|
|
50
|
+
query BuildmyiqGetCourse($id: ID!) {
|
|
51
|
+
buiCourse(id: $id) {
|
|
52
|
+
${COURSE_FIELDS}
|
|
53
|
+
skill { ${SKILL_FIELDS} }
|
|
54
|
+
modules { ${MODULE_FIELDS} lessons { ${LESSON_FIELDS} } }
|
|
55
|
+
lessons { ${LESSON_FIELDS} }
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
`;
|
|
59
|
+
var LIST_COURSES = `
|
|
60
|
+
query BuildmyiqListCourses(
|
|
61
|
+
$level: BuiCourseLevel
|
|
62
|
+
$status: BuiCourseStatus
|
|
63
|
+
$pagination: PaginationInput
|
|
64
|
+
) {
|
|
65
|
+
buiListCourses(level: $level, status: $status, pagination: $pagination) {
|
|
66
|
+
items { ${COURSE_FIELDS} }
|
|
67
|
+
pageInfo { ${PAGE_INFO_FIELDS} }
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
`;
|
|
71
|
+
var CREATE_COURSE = `
|
|
72
|
+
mutation BuildmyiqCreateCourse($input: BuiCreateCourseInput!) {
|
|
73
|
+
buiCreateCourse(input: $input) {
|
|
74
|
+
${COURSE_FIELDS}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
`;
|
|
78
|
+
var UPDATE_COURSE = `
|
|
79
|
+
mutation BuildmyiqUpdateCourse($id: ID!, $input: BuiUpdateCourseInput!) {
|
|
80
|
+
buiUpdateCourse(id: $id, input: $input) {
|
|
81
|
+
${COURSE_FIELDS}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
`;
|
|
85
|
+
var PUBLISH_COURSE = `
|
|
86
|
+
mutation BuildmyiqPublishCourse($id: ID!) {
|
|
87
|
+
buiPublishCourse(id: $id) {
|
|
88
|
+
${COURSE_FIELDS}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
`;
|
|
92
|
+
var ARCHIVE_COURSE = `
|
|
93
|
+
mutation BuildmyiqArchiveCourse($id: ID!) {
|
|
94
|
+
buiArchiveCourse(id: $id)
|
|
95
|
+
}
|
|
96
|
+
`;
|
|
97
|
+
var LIST_MODULES = `
|
|
98
|
+
query BuildmyiqListModules($courseId: ID!) {
|
|
99
|
+
buiListModules(courseId: $courseId) {
|
|
100
|
+
${MODULE_FIELDS}
|
|
101
|
+
lessons { ${LESSON_FIELDS} }
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
`;
|
|
105
|
+
var CREATE_MODULE = `
|
|
106
|
+
mutation BuildmyiqCreateModule($input: BuiCreateModuleInput!) {
|
|
107
|
+
buiCreateModule(input: $input) {
|
|
108
|
+
${MODULE_FIELDS}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
`;
|
|
112
|
+
var LIST_LESSONS = `
|
|
113
|
+
query BuildmyiqListLessons($courseId: ID!, $moduleId: ID) {
|
|
114
|
+
buiListLessons(courseId: $courseId, moduleId: $moduleId) {
|
|
115
|
+
${LESSON_FIELDS}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
`;
|
|
119
|
+
var CREATE_LESSON = `
|
|
120
|
+
mutation BuildmyiqCreateLesson($input: BuiCreateLessonInput!) {
|
|
121
|
+
buiCreateLesson(input: $input) {
|
|
122
|
+
${LESSON_FIELDS}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
`;
|
|
126
|
+
var LIST_SKILLS = `
|
|
127
|
+
query BuildmyiqListSkills($category: BuiSkillCategory) {
|
|
128
|
+
buiListSkills(category: $category) {
|
|
129
|
+
${SKILL_FIELDS}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
`;
|
|
133
|
+
var CREATE_SKILL = `
|
|
134
|
+
mutation BuildmyiqCreateSkill($input: BuiCreateSkillInput!) {
|
|
135
|
+
buiCreateSkill(input: $input) {
|
|
136
|
+
${SKILL_FIELDS}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
`;
|
|
140
|
+
var GET_ENROLLMENT = `
|
|
141
|
+
query BuildmyiqGetEnrollment($id: ID!) {
|
|
142
|
+
buiEnrollment(id: $id) {
|
|
143
|
+
${ENROLLMENT_FIELDS}
|
|
144
|
+
cohort { ${COHORT_FIELDS} }
|
|
145
|
+
lessonProgress { ${LESSON_PROGRESS_FIELDS} }
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
`;
|
|
149
|
+
var LIST_ENROLLMENTS = `
|
|
150
|
+
query BuildmyiqListEnrollments(
|
|
151
|
+
$courseId: ID
|
|
152
|
+
$learnerId: ID
|
|
153
|
+
$status: BuiEnrollmentStatus
|
|
154
|
+
$pagination: PaginationInput
|
|
155
|
+
) {
|
|
156
|
+
buiListEnrollments(
|
|
157
|
+
courseId: $courseId
|
|
158
|
+
learnerId: $learnerId
|
|
159
|
+
status: $status
|
|
160
|
+
pagination: $pagination
|
|
161
|
+
) {
|
|
162
|
+
items { ${ENROLLMENT_FIELDS} }
|
|
163
|
+
pageInfo { ${PAGE_INFO_FIELDS} }
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
`;
|
|
167
|
+
var ENROLL = `
|
|
168
|
+
mutation BuildmyiqEnroll($input: BuiEnrollInput!) {
|
|
169
|
+
buiEnroll(input: $input) {
|
|
170
|
+
${ENROLLMENT_FIELDS}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
`;
|
|
174
|
+
var TRANSITION_ENROLLMENT = `
|
|
175
|
+
mutation BuildmyiqTransitionEnrollment($id: ID!, $status: BuiEnrollmentStatus!) {
|
|
176
|
+
buiTransitionEnrollment(id: $id, status: $status) {
|
|
177
|
+
${ENROLLMENT_FIELDS}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
`;
|
|
181
|
+
var RECORD_LESSON_PROGRESS = `
|
|
182
|
+
mutation BuildmyiqRecordLessonProgress($input: BuiRecordLessonProgressInput!) {
|
|
183
|
+
buiRecordLessonProgress(input: $input) {
|
|
184
|
+
${LESSON_PROGRESS_FIELDS}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
`;
|
|
188
|
+
var LIST_COHORTS = `
|
|
189
|
+
query BuildmyiqListCohorts($courseId: ID, $status: BuiCohortStatus) {
|
|
190
|
+
buiListCohorts(courseId: $courseId, status: $status) {
|
|
191
|
+
${COHORT_FIELDS}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
`;
|
|
195
|
+
var CREATE_COHORT = `
|
|
196
|
+
mutation BuildmyiqCreateCohort($input: BuiCreateCohortInput!) {
|
|
197
|
+
buiCreateCohort(input: $input) {
|
|
198
|
+
${COHORT_FIELDS}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
`;
|
|
202
|
+
var LIST_CERTIFICATES = `
|
|
203
|
+
query BuildmyiqListCertificates($courseId: ID, $learnerId: ID) {
|
|
204
|
+
buiListCertificates(courseId: $courseId, learnerId: $learnerId) {
|
|
205
|
+
${CERTIFICATE_FIELDS}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
`;
|
|
209
|
+
var ISSUE_CERTIFICATE = `
|
|
210
|
+
mutation BuildmyiqIssueCertificate($input: BuiIssueCertificateInput!) {
|
|
211
|
+
buiIssueCertificate(input: $input) {
|
|
212
|
+
${CERTIFICATE_FIELDS}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
`;
|
|
216
|
+
var REVOKE_CERTIFICATE = `
|
|
217
|
+
mutation BuildmyiqRevokeCertificate($id: ID!) {
|
|
218
|
+
buiRevokeCertificate(id: $id) {
|
|
219
|
+
${CERTIFICATE_FIELDS}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
`;
|
|
223
|
+
var GET_ASSESSMENT = `
|
|
224
|
+
query BuildmyiqGetAssessment($id: ID!) {
|
|
225
|
+
buiAssessment(id: $id) {
|
|
226
|
+
${ASSESSMENT_FIELDS}
|
|
227
|
+
questions { ${QUESTION_FIELDS} }
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
`;
|
|
231
|
+
var LIST_ASSESSMENTS = `
|
|
232
|
+
query BuildmyiqListAssessments($courseId: ID!, $status: BuiAssessmentStatus) {
|
|
233
|
+
buiListAssessments(courseId: $courseId, status: $status) {
|
|
234
|
+
${ASSESSMENT_FIELDS}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
`;
|
|
238
|
+
var CREATE_ASSESSMENT = `
|
|
239
|
+
mutation BuildmyiqCreateAssessment($input: BuiCreateAssessmentInput!) {
|
|
240
|
+
buiCreateAssessment(input: $input) {
|
|
241
|
+
${ASSESSMENT_FIELDS}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
`;
|
|
245
|
+
var PUBLISH_ASSESSMENT = `
|
|
246
|
+
mutation BuildmyiqPublishAssessment($id: ID!) {
|
|
247
|
+
buiPublishAssessment(id: $id) {
|
|
248
|
+
${ASSESSMENT_FIELDS}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
`;
|
|
252
|
+
var CREATE_QUESTION = `
|
|
253
|
+
mutation BuildmyiqCreateQuestion($input: BuiCreateQuestionInput!) {
|
|
254
|
+
buiCreateQuestion(input: $input) {
|
|
255
|
+
${QUESTION_FIELDS}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
`;
|
|
259
|
+
var LIST_SUBMISSIONS = `
|
|
260
|
+
query BuildmyiqListSubmissions(
|
|
261
|
+
$assessmentId: ID
|
|
262
|
+
$learnerId: ID
|
|
263
|
+
$status: BuiSubmissionStatus
|
|
264
|
+
$pagination: PaginationInput
|
|
265
|
+
) {
|
|
266
|
+
buiListSubmissions(
|
|
267
|
+
assessmentId: $assessmentId
|
|
268
|
+
learnerId: $learnerId
|
|
269
|
+
status: $status
|
|
270
|
+
pagination: $pagination
|
|
271
|
+
) {
|
|
272
|
+
items { ${SUBMISSION_FIELDS} }
|
|
273
|
+
pageInfo { ${PAGE_INFO_FIELDS} }
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
`;
|
|
277
|
+
var SUBMIT_ASSESSMENT = `
|
|
278
|
+
mutation BuildmyiqSubmitAssessment($input: BuiSubmitAssessmentInput!) {
|
|
279
|
+
buiSubmitAssessment(input: $input) {
|
|
280
|
+
${SUBMISSION_FIELDS}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
`;
|
|
284
|
+
var GRADE_SUBMISSION = `
|
|
285
|
+
mutation BuildmyiqGradeSubmission($id: ID!, $input: BuiGradeSubmissionInput!) {
|
|
286
|
+
buiGradeSubmission(id: $id, input: $input) {
|
|
287
|
+
${SUBMISSION_FIELDS}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
`;
|
|
291
|
+
var HEALTH = `
|
|
292
|
+
query BuildmyiqHealth {
|
|
293
|
+
buiHealth
|
|
294
|
+
}
|
|
295
|
+
`;
|
|
296
|
+
var CourseLevelEnum = z.enum(["BEGINNER", "INTERMEDIATE", "ADVANCED", "EXPERT"]).describe("Difficulty level of a course");
|
|
297
|
+
var CourseStatusEnum = z.enum(["DRAFT", "IN_REVIEW", "PUBLISHED", "ARCHIVED"]).describe("Authoring lifecycle status of a course");
|
|
298
|
+
var SkillCategoryEnum = z.enum(["BUSINESS", "COGNITIVE", "CREATIVE", "LANGUAGE", "SOCIAL", "TECHNICAL"]).describe("Category of a skill/competency");
|
|
299
|
+
var LessonTypeEnum = z.enum(["ARTICLE", "ASSIGNMENT", "INTERACTIVE", "LIVE_SESSION", "QUIZ", "VIDEO"]).describe("Delivery type of a lesson");
|
|
300
|
+
var EnrollmentStatusEnum = z.enum(["ENROLLED", "IN_PROGRESS", "COMPLETED", "DROPPED", "EXPIRED"]).describe("Lifecycle status of a learner enrollment");
|
|
301
|
+
var ProgressStatusEnum = z.enum(["NOT_STARTED", "IN_PROGRESS", "COMPLETED"]).describe("Per-lesson progress status within an enrollment");
|
|
302
|
+
var CohortStatusEnum = z.enum(["SCHEDULED", "ENROLLING", "ACTIVE", "COMPLETED", "CANCELLED"]).describe("Lifecycle status of a cohort");
|
|
303
|
+
var AssessmentTypeEnum = z.enum(["ASSIGNMENT", "EXAM", "PRACTICAL", "QUIZ", "SURVEY"]).describe("Type of an assessment");
|
|
304
|
+
var AssessmentStatusEnum = z.enum(["DRAFT", "PUBLISHED", "ARCHIVED"]).describe("Lifecycle status of an assessment");
|
|
305
|
+
var QuestionTypeEnum = z.enum(["CODE", "ESSAY", "MULTIPLE_CHOICE", "SHORT_ANSWER", "SINGLE_CHOICE", "TRUE_FALSE"]).describe("Type of an assessment question");
|
|
306
|
+
var SubmissionStatusEnum = z.enum(["IN_PROGRESS", "SUBMITTED", "GRADING", "GRADED", "RETURNED"]).describe("Lifecycle status of an assessment submission");
|
|
307
|
+
var JsonObject = z.record(z.string(), z.unknown());
|
|
308
|
+
var JsonValue = z.union([JsonObject, z.array(z.unknown())]).describe("Arbitrary JSON value (object or array)");
|
|
309
|
+
|
|
310
|
+
// src/modules/buildmyiq/tools/courses.ts
|
|
311
|
+
function registerCourseTools(server, ctx, track) {
|
|
312
|
+
server.registerTool(
|
|
313
|
+
track(mcpToolName(ctx.productName, "courses_get")),
|
|
314
|
+
{
|
|
315
|
+
title: "Get Course",
|
|
316
|
+
description: "Fetch a single BuildMyIQ course by ID, including its skill, modules (with lessons), and flat lesson list.",
|
|
317
|
+
inputSchema: { id: z.string().describe("Course ID") }
|
|
318
|
+
},
|
|
319
|
+
async ({ id }) => {
|
|
320
|
+
try {
|
|
321
|
+
const data = await ctx.gql.workspace.request(GET_COURSE, { id });
|
|
322
|
+
if (!data.buiCourse) return errorResult(`Course ${id} not found`);
|
|
323
|
+
return jsonResult(data.buiCourse);
|
|
324
|
+
} catch (error) {
|
|
325
|
+
ctx.logger.error("buildmyiq courses_get failed", { error: String(error) });
|
|
326
|
+
return errorResult(
|
|
327
|
+
`Failed to get course: ${error instanceof Error ? error.message : String(error)}`
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
);
|
|
332
|
+
server.registerTool(
|
|
333
|
+
track(mcpToolName(ctx.productName, "courses_list")),
|
|
334
|
+
{
|
|
335
|
+
title: "List Courses",
|
|
336
|
+
description: "List courses in the bound workspace with optional level/status filters and cursor/limit/offset pagination. Returns { items, pageInfo }.",
|
|
337
|
+
inputSchema: {
|
|
338
|
+
level: CourseLevelEnum.optional().describe("Filter by difficulty level"),
|
|
339
|
+
status: CourseStatusEnum.optional().describe("Filter by authoring lifecycle status"),
|
|
340
|
+
limit: z.number().int().optional().describe("Page size (default 50)"),
|
|
341
|
+
offset: z.number().int().optional().describe("Pagination offset (default 0)"),
|
|
342
|
+
cursor: z.string().optional().describe("Opaque pagination cursor")
|
|
343
|
+
}
|
|
344
|
+
},
|
|
345
|
+
async ({ level, status, limit, offset, cursor }) => {
|
|
346
|
+
try {
|
|
347
|
+
const pagination = {};
|
|
348
|
+
if (limit !== void 0) pagination.limit = limit;
|
|
349
|
+
if (offset !== void 0) pagination.offset = offset;
|
|
350
|
+
if (cursor !== void 0) pagination.cursor = cursor;
|
|
351
|
+
const data = await ctx.gql.workspace.request(LIST_COURSES, {
|
|
352
|
+
level,
|
|
353
|
+
status,
|
|
354
|
+
pagination: Object.keys(pagination).length > 0 ? pagination : void 0
|
|
355
|
+
});
|
|
356
|
+
return jsonResult(data.buiListCourses);
|
|
357
|
+
} catch (error) {
|
|
358
|
+
ctx.logger.error("buildmyiq courses_list failed", { error: String(error) });
|
|
359
|
+
return errorResult(
|
|
360
|
+
`Failed to list courses: ${error instanceof Error ? error.message : String(error)}`
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
);
|
|
365
|
+
server.registerTool(
|
|
366
|
+
track(mcpToolName(ctx.productName, "courses_create")),
|
|
367
|
+
{
|
|
368
|
+
title: "Create Course",
|
|
369
|
+
description: "Create a draft course. `slug` must be unique within the workspace. Reference a cover image by `coverFileId` (a wspace-files-svc File) rather than uploading a blob.",
|
|
370
|
+
inputSchema: {
|
|
371
|
+
title: z.string().min(1).describe("Course title"),
|
|
372
|
+
slug: z.string().min(1).describe("URL slug, unique within the workspace"),
|
|
373
|
+
summary: z.string().optional().describe("Short marketing summary"),
|
|
374
|
+
description: z.string().optional().describe("Full course description"),
|
|
375
|
+
level: CourseLevelEnum.optional().describe("Difficulty level (default BEGINNER)"),
|
|
376
|
+
language: z.string().optional().describe('Primary language (e.g. "en")'),
|
|
377
|
+
estimatedHours: z.number().int().optional().describe("Estimated completion hours"),
|
|
378
|
+
isPublic: z.boolean().optional().describe("Whether the course is publicly discoverable"),
|
|
379
|
+
instructorId: z.string().optional().describe("User ID of the primary instructor"),
|
|
380
|
+
skillId: z.string().optional().describe("Linked skill ID"),
|
|
381
|
+
coverFileId: z.string().optional().describe("wspace-files-svc File ID for the cover image"),
|
|
382
|
+
tags: z.array(z.string()).optional().describe("Discovery tags (default [])")
|
|
383
|
+
}
|
|
384
|
+
},
|
|
385
|
+
async ({
|
|
386
|
+
title,
|
|
387
|
+
slug,
|
|
388
|
+
summary,
|
|
389
|
+
description,
|
|
390
|
+
level,
|
|
391
|
+
language,
|
|
392
|
+
estimatedHours,
|
|
393
|
+
isPublic,
|
|
394
|
+
instructorId,
|
|
395
|
+
skillId,
|
|
396
|
+
coverFileId,
|
|
397
|
+
tags
|
|
398
|
+
}) => {
|
|
399
|
+
try {
|
|
400
|
+
const input = { title, slug };
|
|
401
|
+
if (summary !== void 0) input.summary = summary;
|
|
402
|
+
if (description !== void 0) input.description = description;
|
|
403
|
+
if (level !== void 0) input.level = level;
|
|
404
|
+
if (language !== void 0) input.language = language;
|
|
405
|
+
if (estimatedHours !== void 0) input.estimatedHours = estimatedHours;
|
|
406
|
+
if (isPublic !== void 0) input.isPublic = isPublic;
|
|
407
|
+
if (instructorId !== void 0) input.instructorId = instructorId;
|
|
408
|
+
if (skillId !== void 0) input.skillId = skillId;
|
|
409
|
+
if (coverFileId !== void 0) input.coverFileId = coverFileId;
|
|
410
|
+
if (tags !== void 0) input.tags = tags;
|
|
411
|
+
const data = await ctx.gql.workspace.request(CREATE_COURSE, { input });
|
|
412
|
+
return jsonResult(data.buiCreateCourse);
|
|
413
|
+
} catch (error) {
|
|
414
|
+
ctx.logger.error("buildmyiq courses_create failed", { error: String(error) });
|
|
415
|
+
return errorResult(
|
|
416
|
+
`Failed to create course: ${error instanceof Error ? error.message : String(error)}`
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
);
|
|
421
|
+
server.registerTool(
|
|
422
|
+
track(mcpToolName(ctx.productName, "courses_update")),
|
|
423
|
+
{
|
|
424
|
+
title: "Update Course",
|
|
425
|
+
description: "Update a course. At least one mutable field must be provided. Use courses_publish/courses_archive for lifecycle transitions rather than setting status here.",
|
|
426
|
+
inputSchema: {
|
|
427
|
+
id: z.string().describe("Course ID"),
|
|
428
|
+
title: z.string().optional().describe("Updated title"),
|
|
429
|
+
summary: z.string().optional().describe("Updated summary"),
|
|
430
|
+
description: z.string().optional().describe("Updated description"),
|
|
431
|
+
level: CourseLevelEnum.optional().describe("Updated difficulty level"),
|
|
432
|
+
language: z.string().optional().describe("Updated language"),
|
|
433
|
+
estimatedHours: z.number().int().optional().describe("Updated estimated hours"),
|
|
434
|
+
isPublic: z.boolean().optional().describe("Updated public visibility"),
|
|
435
|
+
instructorId: z.string().optional().describe("Updated instructor user ID"),
|
|
436
|
+
skillId: z.string().optional().describe("Updated linked skill ID"),
|
|
437
|
+
coverFileId: z.string().optional().describe("Updated cover File ID"),
|
|
438
|
+
tags: z.array(z.string()).optional().describe("Updated discovery tags")
|
|
439
|
+
}
|
|
440
|
+
},
|
|
441
|
+
async ({
|
|
442
|
+
id,
|
|
443
|
+
title,
|
|
444
|
+
summary,
|
|
445
|
+
description,
|
|
446
|
+
level,
|
|
447
|
+
language,
|
|
448
|
+
estimatedHours,
|
|
449
|
+
isPublic,
|
|
450
|
+
instructorId,
|
|
451
|
+
skillId,
|
|
452
|
+
coverFileId,
|
|
453
|
+
tags
|
|
454
|
+
}) => {
|
|
455
|
+
try {
|
|
456
|
+
const input = {};
|
|
457
|
+
if (title !== void 0) input.title = title;
|
|
458
|
+
if (summary !== void 0) input.summary = summary;
|
|
459
|
+
if (description !== void 0) input.description = description;
|
|
460
|
+
if (level !== void 0) input.level = level;
|
|
461
|
+
if (language !== void 0) input.language = language;
|
|
462
|
+
if (estimatedHours !== void 0) input.estimatedHours = estimatedHours;
|
|
463
|
+
if (isPublic !== void 0) input.isPublic = isPublic;
|
|
464
|
+
if (instructorId !== void 0) input.instructorId = instructorId;
|
|
465
|
+
if (skillId !== void 0) input.skillId = skillId;
|
|
466
|
+
if (coverFileId !== void 0) input.coverFileId = coverFileId;
|
|
467
|
+
if (tags !== void 0) input.tags = tags;
|
|
468
|
+
if (Object.keys(input).length === 0) {
|
|
469
|
+
return errorResult("No updates specified \u2014 provide at least one mutable course field");
|
|
470
|
+
}
|
|
471
|
+
const data = await ctx.gql.workspace.request(UPDATE_COURSE, { id, input });
|
|
472
|
+
return jsonResult(data.buiUpdateCourse);
|
|
473
|
+
} catch (error) {
|
|
474
|
+
ctx.logger.error("buildmyiq courses_update failed", { error: String(error) });
|
|
475
|
+
return errorResult(
|
|
476
|
+
`Failed to update course: ${error instanceof Error ? error.message : String(error)}`
|
|
477
|
+
);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
);
|
|
481
|
+
server.registerTool(
|
|
482
|
+
track(mcpToolName(ctx.productName, "courses_publish")),
|
|
483
|
+
{
|
|
484
|
+
title: "Publish Course",
|
|
485
|
+
description: "Publish a course through its authoring lifecycle (validated DRAFT \u2192 IN_REVIEW \u2192 PUBLISHED transitions).",
|
|
486
|
+
inputSchema: { id: z.string().describe("Course ID") }
|
|
487
|
+
},
|
|
488
|
+
async ({ id }) => {
|
|
489
|
+
try {
|
|
490
|
+
const data = await ctx.gql.workspace.request(PUBLISH_COURSE, { id });
|
|
491
|
+
return jsonResult(data.buiPublishCourse);
|
|
492
|
+
} catch (error) {
|
|
493
|
+
ctx.logger.error("buildmyiq courses_publish failed", { error: String(error) });
|
|
494
|
+
return errorResult(
|
|
495
|
+
`Failed to publish course: ${error instanceof Error ? error.message : String(error)}`
|
|
496
|
+
);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
);
|
|
500
|
+
server.registerTool(
|
|
501
|
+
track(mcpToolName(ctx.productName, "courses_archive")),
|
|
502
|
+
{
|
|
503
|
+
title: "Archive Course",
|
|
504
|
+
description: "Archive a course (soft-retire). Returns true on success.",
|
|
505
|
+
inputSchema: { id: z.string().describe("Course ID") }
|
|
506
|
+
},
|
|
507
|
+
async ({ id }) => {
|
|
508
|
+
try {
|
|
509
|
+
const data = await ctx.gql.workspace.request(
|
|
510
|
+
ARCHIVE_COURSE,
|
|
511
|
+
{ id }
|
|
512
|
+
);
|
|
513
|
+
return jsonResult({ archived: data.buiArchiveCourse });
|
|
514
|
+
} catch (error) {
|
|
515
|
+
ctx.logger.error("buildmyiq courses_archive failed", { error: String(error) });
|
|
516
|
+
return errorResult(
|
|
517
|
+
`Failed to archive course: ${error instanceof Error ? error.message : String(error)}`
|
|
518
|
+
);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
);
|
|
522
|
+
}
|
|
523
|
+
function registerModuleTools(server, ctx, track) {
|
|
524
|
+
server.registerTool(
|
|
525
|
+
track(mcpToolName(ctx.productName, "modules_list")),
|
|
526
|
+
{
|
|
527
|
+
title: "List Course Modules",
|
|
528
|
+
description: "List the modules (sections) of a course in position order, each with its lessons.",
|
|
529
|
+
inputSchema: { courseId: z.string().describe("Course ID") }
|
|
530
|
+
},
|
|
531
|
+
async ({ courseId }) => {
|
|
532
|
+
try {
|
|
533
|
+
const data = await ctx.gql.workspace.request(LIST_MODULES, { courseId });
|
|
534
|
+
return jsonResult(data.buiListModules);
|
|
535
|
+
} catch (error) {
|
|
536
|
+
ctx.logger.error("buildmyiq modules_list failed", { error: String(error) });
|
|
537
|
+
return errorResult(
|
|
538
|
+
`Failed to list modules: ${error instanceof Error ? error.message : String(error)}`
|
|
539
|
+
);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
);
|
|
543
|
+
server.registerTool(
|
|
544
|
+
track(mcpToolName(ctx.productName, "modules_create")),
|
|
545
|
+
{
|
|
546
|
+
title: "Create Course Module",
|
|
547
|
+
description: "Add a module (section) to a course. `position` orders it within the course.",
|
|
548
|
+
inputSchema: {
|
|
549
|
+
courseId: z.string().min(1).describe("Owning course ID"),
|
|
550
|
+
title: z.string().min(1).describe("Module title"),
|
|
551
|
+
description: z.string().optional().describe("Module description"),
|
|
552
|
+
position: z.number().int().optional().describe("Ordering position within the course")
|
|
553
|
+
}
|
|
554
|
+
},
|
|
555
|
+
async ({ courseId, title, description, position }) => {
|
|
556
|
+
try {
|
|
557
|
+
const input = { courseId, title };
|
|
558
|
+
if (description !== void 0) input.description = description;
|
|
559
|
+
if (position !== void 0) input.position = position;
|
|
560
|
+
const data = await ctx.gql.workspace.request(CREATE_MODULE, { input });
|
|
561
|
+
return jsonResult(data.buiCreateModule);
|
|
562
|
+
} catch (error) {
|
|
563
|
+
ctx.logger.error("buildmyiq modules_create failed", { error: String(error) });
|
|
564
|
+
return errorResult(
|
|
565
|
+
`Failed to create module: ${error instanceof Error ? error.message : String(error)}`
|
|
566
|
+
);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
);
|
|
570
|
+
}
|
|
571
|
+
function registerLessonTools(server, ctx, track) {
|
|
572
|
+
server.registerTool(
|
|
573
|
+
track(mcpToolName(ctx.productName, "lessons_list")),
|
|
574
|
+
{
|
|
575
|
+
title: "List Lessons",
|
|
576
|
+
description: "List the lessons of a course in position order, optionally narrowed to a single module.",
|
|
577
|
+
inputSchema: {
|
|
578
|
+
courseId: z.string().describe("Course ID"),
|
|
579
|
+
moduleId: z.string().optional().describe("Filter to a single module")
|
|
580
|
+
}
|
|
581
|
+
},
|
|
582
|
+
async ({ courseId, moduleId }) => {
|
|
583
|
+
try {
|
|
584
|
+
const data = await ctx.gql.workspace.request(LIST_LESSONS, { courseId, moduleId });
|
|
585
|
+
return jsonResult(data.buiListLessons);
|
|
586
|
+
} catch (error) {
|
|
587
|
+
ctx.logger.error("buildmyiq lessons_list failed", { error: String(error) });
|
|
588
|
+
return errorResult(
|
|
589
|
+
`Failed to list lessons: ${error instanceof Error ? error.message : String(error)}`
|
|
590
|
+
);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
);
|
|
594
|
+
server.registerTool(
|
|
595
|
+
track(mcpToolName(ctx.productName, "lessons_create")),
|
|
596
|
+
{
|
|
597
|
+
title: "Create Lesson",
|
|
598
|
+
description: "Add a lesson to a course (optionally within a module). Reference media (video/asset) by `mediaFileId` (a wspace-files-svc File) rather than uploading a blob.",
|
|
599
|
+
inputSchema: {
|
|
600
|
+
courseId: z.string().min(1).describe("Owning course ID"),
|
|
601
|
+
title: z.string().min(1).describe("Lesson title"),
|
|
602
|
+
type: LessonTypeEnum.optional().describe("Lesson delivery type (default ARTICLE)"),
|
|
603
|
+
moduleId: z.string().optional().describe("Owning module ID (optional)"),
|
|
604
|
+
content: z.string().optional().describe("Lesson body content (markdown/HTML/text)"),
|
|
605
|
+
durationMin: z.number().int().optional().describe("Estimated duration in minutes"),
|
|
606
|
+
isPreview: z.boolean().optional().describe("Whether the lesson is a free preview"),
|
|
607
|
+
position: z.number().int().optional().describe("Ordering position within the course/module"),
|
|
608
|
+
mediaFileId: z.string().optional().describe("wspace-files-svc File ID for the lesson media")
|
|
609
|
+
}
|
|
610
|
+
},
|
|
611
|
+
async ({
|
|
612
|
+
courseId,
|
|
613
|
+
title,
|
|
614
|
+
type,
|
|
615
|
+
moduleId,
|
|
616
|
+
content,
|
|
617
|
+
durationMin,
|
|
618
|
+
isPreview,
|
|
619
|
+
position,
|
|
620
|
+
mediaFileId
|
|
621
|
+
}) => {
|
|
622
|
+
try {
|
|
623
|
+
const input = { courseId, title };
|
|
624
|
+
if (type !== void 0) input.type = type;
|
|
625
|
+
if (moduleId !== void 0) input.moduleId = moduleId;
|
|
626
|
+
if (content !== void 0) input.content = content;
|
|
627
|
+
if (durationMin !== void 0) input.durationMin = durationMin;
|
|
628
|
+
if (isPreview !== void 0) input.isPreview = isPreview;
|
|
629
|
+
if (position !== void 0) input.position = position;
|
|
630
|
+
if (mediaFileId !== void 0) input.mediaFileId = mediaFileId;
|
|
631
|
+
const data = await ctx.gql.workspace.request(CREATE_LESSON, { input });
|
|
632
|
+
return jsonResult(data.buiCreateLesson);
|
|
633
|
+
} catch (error) {
|
|
634
|
+
ctx.logger.error("buildmyiq lessons_create failed", { error: String(error) });
|
|
635
|
+
return errorResult(
|
|
636
|
+
`Failed to create lesson: ${error instanceof Error ? error.message : String(error)}`
|
|
637
|
+
);
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
);
|
|
641
|
+
}
|
|
642
|
+
function registerSkillTools(server, ctx, track) {
|
|
643
|
+
server.registerTool(
|
|
644
|
+
track(mcpToolName(ctx.productName, "skills_list")),
|
|
645
|
+
{
|
|
646
|
+
title: "List Skills",
|
|
647
|
+
description: "List skills in the bound workspace, optionally filtered by category.",
|
|
648
|
+
inputSchema: {
|
|
649
|
+
category: SkillCategoryEnum.optional().describe("Filter by skill category")
|
|
650
|
+
}
|
|
651
|
+
},
|
|
652
|
+
async ({ category }) => {
|
|
653
|
+
try {
|
|
654
|
+
const data = await ctx.gql.workspace.request(LIST_SKILLS, { category });
|
|
655
|
+
return jsonResult(data.buiListSkills);
|
|
656
|
+
} catch (error) {
|
|
657
|
+
ctx.logger.error("buildmyiq skills_list failed", { error: String(error) });
|
|
658
|
+
return errorResult(
|
|
659
|
+
`Failed to list skills: ${error instanceof Error ? error.message : String(error)}`
|
|
660
|
+
);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
);
|
|
664
|
+
server.registerTool(
|
|
665
|
+
track(mcpToolName(ctx.productName, "skills_create")),
|
|
666
|
+
{
|
|
667
|
+
title: "Create Skill",
|
|
668
|
+
description: "Create a skill/competency that courses can be tagged against.",
|
|
669
|
+
inputSchema: {
|
|
670
|
+
name: z.string().min(1).describe("Skill name"),
|
|
671
|
+
category: SkillCategoryEnum.optional().describe("Skill category (default TECHNICAL)"),
|
|
672
|
+
description: z.string().optional().describe("Skill description")
|
|
673
|
+
}
|
|
674
|
+
},
|
|
675
|
+
async ({ name, category, description }) => {
|
|
676
|
+
try {
|
|
677
|
+
const input = { name };
|
|
678
|
+
if (category !== void 0) input.category = category;
|
|
679
|
+
if (description !== void 0) input.description = description;
|
|
680
|
+
const data = await ctx.gql.workspace.request(CREATE_SKILL, { input });
|
|
681
|
+
return jsonResult(data.buiCreateSkill);
|
|
682
|
+
} catch (error) {
|
|
683
|
+
ctx.logger.error("buildmyiq skills_create failed", { error: String(error) });
|
|
684
|
+
return errorResult(
|
|
685
|
+
`Failed to create skill: ${error instanceof Error ? error.message : String(error)}`
|
|
686
|
+
);
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
);
|
|
690
|
+
}
|
|
691
|
+
function registerEnrollmentTools(server, ctx, track) {
|
|
692
|
+
server.registerTool(
|
|
693
|
+
track(mcpToolName(ctx.productName, "enrollments_get")),
|
|
694
|
+
{
|
|
695
|
+
title: "Get Enrollment",
|
|
696
|
+
description: "Fetch a single enrollment by ID, including its cohort and per-lesson progress records.",
|
|
697
|
+
inputSchema: { id: z.string().describe("Enrollment ID") }
|
|
698
|
+
},
|
|
699
|
+
async ({ id }) => {
|
|
700
|
+
try {
|
|
701
|
+
const data = await ctx.gql.workspace.request(GET_ENROLLMENT, { id });
|
|
702
|
+
if (!data.buiEnrollment) return errorResult(`Enrollment ${id} not found`);
|
|
703
|
+
return jsonResult(data.buiEnrollment);
|
|
704
|
+
} catch (error) {
|
|
705
|
+
ctx.logger.error("buildmyiq enrollments_get failed", { error: String(error) });
|
|
706
|
+
return errorResult(
|
|
707
|
+
`Failed to get enrollment: ${error instanceof Error ? error.message : String(error)}`
|
|
708
|
+
);
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
);
|
|
712
|
+
server.registerTool(
|
|
713
|
+
track(mcpToolName(ctx.productName, "enrollments_list")),
|
|
714
|
+
{
|
|
715
|
+
title: "List Enrollments",
|
|
716
|
+
description: "List enrollments, optionally filtered by course, learner, and/or status, with cursor/limit/offset pagination. Returns { items, pageInfo }.",
|
|
717
|
+
inputSchema: {
|
|
718
|
+
courseId: z.string().optional().describe("Filter by course ID"),
|
|
719
|
+
learnerId: z.string().optional().describe("Filter by learner user ID"),
|
|
720
|
+
status: EnrollmentStatusEnum.optional().describe("Filter by enrollment status"),
|
|
721
|
+
limit: z.number().int().optional().describe("Page size (default 50)"),
|
|
722
|
+
offset: z.number().int().optional().describe("Pagination offset (default 0)"),
|
|
723
|
+
cursor: z.string().optional().describe("Opaque pagination cursor")
|
|
724
|
+
}
|
|
725
|
+
},
|
|
726
|
+
async ({ courseId, learnerId, status, limit, offset, cursor }) => {
|
|
727
|
+
try {
|
|
728
|
+
const pagination = {};
|
|
729
|
+
if (limit !== void 0) pagination.limit = limit;
|
|
730
|
+
if (offset !== void 0) pagination.offset = offset;
|
|
731
|
+
if (cursor !== void 0) pagination.cursor = cursor;
|
|
732
|
+
const data = await ctx.gql.workspace.request(LIST_ENROLLMENTS, {
|
|
733
|
+
courseId,
|
|
734
|
+
learnerId,
|
|
735
|
+
status,
|
|
736
|
+
pagination: Object.keys(pagination).length > 0 ? pagination : void 0
|
|
737
|
+
});
|
|
738
|
+
return jsonResult(data.buiListEnrollments);
|
|
739
|
+
} catch (error) {
|
|
740
|
+
ctx.logger.error("buildmyiq enrollments_list failed", { error: String(error) });
|
|
741
|
+
return errorResult(
|
|
742
|
+
`Failed to list enrollments: ${error instanceof Error ? error.message : String(error)}`
|
|
743
|
+
);
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
);
|
|
747
|
+
server.registerTool(
|
|
748
|
+
track(mcpToolName(ctx.productName, "enrollments_enroll")),
|
|
749
|
+
{
|
|
750
|
+
title: "Enroll Learner",
|
|
751
|
+
description: "Enroll a learner in a course (optionally within a cohort). If `learnerId` is omitted the calling actor is enrolled. `expiresAt` is an optional access-expiry timestamp.",
|
|
752
|
+
inputSchema: {
|
|
753
|
+
courseId: z.string().min(1).describe("Course to enroll into"),
|
|
754
|
+
learnerId: z.string().optional().describe("Learner user ID (defaults to the caller)"),
|
|
755
|
+
cohortId: z.string().optional().describe("Cohort to enroll within"),
|
|
756
|
+
expiresAt: z.string().optional().describe("Access-expiry timestamp (ISO-8601 DateTime)")
|
|
757
|
+
}
|
|
758
|
+
},
|
|
759
|
+
async ({ courseId, learnerId, cohortId, expiresAt }) => {
|
|
760
|
+
try {
|
|
761
|
+
const input = { courseId };
|
|
762
|
+
if (learnerId !== void 0) input.learnerId = learnerId;
|
|
763
|
+
if (cohortId !== void 0) input.cohortId = cohortId;
|
|
764
|
+
if (expiresAt !== void 0) input.expiresAt = expiresAt;
|
|
765
|
+
const data = await ctx.gql.workspace.request(ENROLL, { input });
|
|
766
|
+
return jsonResult(data.buiEnroll);
|
|
767
|
+
} catch (error) {
|
|
768
|
+
ctx.logger.error("buildmyiq enrollments_enroll failed", { error: String(error) });
|
|
769
|
+
return errorResult(
|
|
770
|
+
`Failed to enroll: ${error instanceof Error ? error.message : String(error)}`
|
|
771
|
+
);
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
);
|
|
775
|
+
server.registerTool(
|
|
776
|
+
track(mcpToolName(ctx.productName, "enrollments_transition")),
|
|
777
|
+
{
|
|
778
|
+
title: "Transition Enrollment",
|
|
779
|
+
description: "Advance an enrollment through its lifecycle via validated transitions (ENROLLED \u2192 IN_PROGRESS \u2192 COMPLETED, or DROPPED/EXPIRED).",
|
|
780
|
+
inputSchema: {
|
|
781
|
+
id: z.string().describe("Enrollment ID"),
|
|
782
|
+
status: EnrollmentStatusEnum.describe("Target enrollment status")
|
|
783
|
+
}
|
|
784
|
+
},
|
|
785
|
+
async ({ id, status }) => {
|
|
786
|
+
try {
|
|
787
|
+
const data = await ctx.gql.workspace.request(TRANSITION_ENROLLMENT, { id, status });
|
|
788
|
+
return jsonResult(data.buiTransitionEnrollment);
|
|
789
|
+
} catch (error) {
|
|
790
|
+
ctx.logger.error("buildmyiq enrollments_transition failed", { error: String(error) });
|
|
791
|
+
return errorResult(
|
|
792
|
+
`Failed to transition enrollment: ${error instanceof Error ? error.message : String(error)}`
|
|
793
|
+
);
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
);
|
|
797
|
+
server.registerTool(
|
|
798
|
+
track(mcpToolName(ctx.productName, "enrollments_record_progress")),
|
|
799
|
+
{
|
|
800
|
+
title: "Record Lesson Progress",
|
|
801
|
+
description: "Record (upsert) a learner\u2019s progress on a lesson within an enrollment \u2014 sets status and accumulates time spent.",
|
|
802
|
+
inputSchema: {
|
|
803
|
+
enrollmentId: z.string().min(1).describe("Enrollment ID"),
|
|
804
|
+
lessonId: z.string().min(1).describe("Lesson ID"),
|
|
805
|
+
status: ProgressStatusEnum.optional().describe("Progress status (default IN_PROGRESS)"),
|
|
806
|
+
secondsSpent: z.number().int().optional().describe("Seconds spent to add to the total")
|
|
807
|
+
}
|
|
808
|
+
},
|
|
809
|
+
async ({ enrollmentId, lessonId, status, secondsSpent }) => {
|
|
810
|
+
try {
|
|
811
|
+
const input = { enrollmentId, lessonId };
|
|
812
|
+
if (status !== void 0) input.status = status;
|
|
813
|
+
if (secondsSpent !== void 0) input.secondsSpent = secondsSpent;
|
|
814
|
+
const data = await ctx.gql.workspace.request(RECORD_LESSON_PROGRESS, { input });
|
|
815
|
+
return jsonResult(data.buiRecordLessonProgress);
|
|
816
|
+
} catch (error) {
|
|
817
|
+
ctx.logger.error("buildmyiq enrollments_record_progress failed", { error: String(error) });
|
|
818
|
+
return errorResult(
|
|
819
|
+
`Failed to record lesson progress: ${error instanceof Error ? error.message : String(error)}`
|
|
820
|
+
);
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
);
|
|
824
|
+
}
|
|
825
|
+
function registerCohortTools(server, ctx, track) {
|
|
826
|
+
server.registerTool(
|
|
827
|
+
track(mcpToolName(ctx.productName, "cohorts_list")),
|
|
828
|
+
{
|
|
829
|
+
title: "List Cohorts",
|
|
830
|
+
description: "List cohorts, optionally filtered by course and/or status.",
|
|
831
|
+
inputSchema: {
|
|
832
|
+
courseId: z.string().optional().describe("Filter by course ID"),
|
|
833
|
+
status: CohortStatusEnum.optional().describe("Filter by cohort status")
|
|
834
|
+
}
|
|
835
|
+
},
|
|
836
|
+
async ({ courseId, status }) => {
|
|
837
|
+
try {
|
|
838
|
+
const data = await ctx.gql.workspace.request(LIST_COHORTS, { courseId, status });
|
|
839
|
+
return jsonResult(data.buiListCohorts);
|
|
840
|
+
} catch (error) {
|
|
841
|
+
ctx.logger.error("buildmyiq cohorts_list failed", { error: String(error) });
|
|
842
|
+
return errorResult(
|
|
843
|
+
`Failed to list cohorts: ${error instanceof Error ? error.message : String(error)}`
|
|
844
|
+
);
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
);
|
|
848
|
+
server.registerTool(
|
|
849
|
+
track(mcpToolName(ctx.productName, "cohorts_create")),
|
|
850
|
+
{
|
|
851
|
+
title: "Create Cohort",
|
|
852
|
+
description: "Create a scheduled cohort for a course with an optional facilitator, capacity, and start/end dates.",
|
|
853
|
+
inputSchema: {
|
|
854
|
+
courseId: z.string().min(1).describe("Owning course ID"),
|
|
855
|
+
name: z.string().min(1).describe("Cohort name"),
|
|
856
|
+
capacity: z.number().int().optional().describe("Maximum number of learners"),
|
|
857
|
+
facilitatorId: z.string().optional().describe("Facilitator user ID"),
|
|
858
|
+
startDate: z.string().optional().describe("Start date (ISO-8601 DateTime)"),
|
|
859
|
+
endDate: z.string().optional().describe("End date (ISO-8601 DateTime)")
|
|
860
|
+
}
|
|
861
|
+
},
|
|
862
|
+
async ({ courseId, name, capacity, facilitatorId, startDate, endDate }) => {
|
|
863
|
+
try {
|
|
864
|
+
const input = { courseId, name };
|
|
865
|
+
if (capacity !== void 0) input.capacity = capacity;
|
|
866
|
+
if (facilitatorId !== void 0) input.facilitatorId = facilitatorId;
|
|
867
|
+
if (startDate !== void 0) input.startDate = startDate;
|
|
868
|
+
if (endDate !== void 0) input.endDate = endDate;
|
|
869
|
+
const data = await ctx.gql.workspace.request(CREATE_COHORT, { input });
|
|
870
|
+
return jsonResult(data.buiCreateCohort);
|
|
871
|
+
} catch (error) {
|
|
872
|
+
ctx.logger.error("buildmyiq cohorts_create failed", { error: String(error) });
|
|
873
|
+
return errorResult(
|
|
874
|
+
`Failed to create cohort: ${error instanceof Error ? error.message : String(error)}`
|
|
875
|
+
);
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
);
|
|
879
|
+
}
|
|
880
|
+
function registerCertificateTools(server, ctx, track) {
|
|
881
|
+
server.registerTool(
|
|
882
|
+
track(mcpToolName(ctx.productName, "certificates_list")),
|
|
883
|
+
{
|
|
884
|
+
title: "List Certificates",
|
|
885
|
+
description: "List issued certificates, optionally filtered by course and/or learner.",
|
|
886
|
+
inputSchema: {
|
|
887
|
+
courseId: z.string().optional().describe("Filter by course ID"),
|
|
888
|
+
learnerId: z.string().optional().describe("Filter by learner user ID")
|
|
889
|
+
}
|
|
890
|
+
},
|
|
891
|
+
async ({ courseId, learnerId }) => {
|
|
892
|
+
try {
|
|
893
|
+
const data = await ctx.gql.workspace.request(LIST_CERTIFICATES, { courseId, learnerId });
|
|
894
|
+
return jsonResult(data.buiListCertificates);
|
|
895
|
+
} catch (error) {
|
|
896
|
+
ctx.logger.error("buildmyiq certificates_list failed", { error: String(error) });
|
|
897
|
+
return errorResult(
|
|
898
|
+
`Failed to list certificates: ${error instanceof Error ? error.message : String(error)}`
|
|
899
|
+
);
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
);
|
|
903
|
+
server.registerTool(
|
|
904
|
+
track(mcpToolName(ctx.productName, "certificates_issue")),
|
|
905
|
+
{
|
|
906
|
+
title: "Issue Certificate",
|
|
907
|
+
description: "Issue a completion certificate to a learner for a course. `serial` must be unique. Reference the rendered PDF by `fileId` (a wspace-files-svc File).",
|
|
908
|
+
inputSchema: {
|
|
909
|
+
courseId: z.string().min(1).describe("Course the certificate is for"),
|
|
910
|
+
learnerId: z.string().min(1).describe("Recipient learner user ID"),
|
|
911
|
+
serial: z.string().min(1).describe("Unique certificate serial number"),
|
|
912
|
+
enrollmentId: z.string().optional().describe("Source enrollment ID"),
|
|
913
|
+
finalScore: z.number().optional().describe("Final score recorded on the certificate"),
|
|
914
|
+
fileId: z.string().optional().describe("wspace-files-svc File ID for the rendered PDF"),
|
|
915
|
+
expiresAt: z.string().optional().describe("Expiry timestamp (ISO-8601 DateTime)")
|
|
916
|
+
}
|
|
917
|
+
},
|
|
918
|
+
async ({ courseId, learnerId, serial, enrollmentId, finalScore, fileId, expiresAt }) => {
|
|
919
|
+
try {
|
|
920
|
+
const input = { courseId, learnerId, serial };
|
|
921
|
+
if (enrollmentId !== void 0) input.enrollmentId = enrollmentId;
|
|
922
|
+
if (finalScore !== void 0) input.finalScore = finalScore;
|
|
923
|
+
if (fileId !== void 0) input.fileId = fileId;
|
|
924
|
+
if (expiresAt !== void 0) input.expiresAt = expiresAt;
|
|
925
|
+
const data = await ctx.gql.workspace.request(ISSUE_CERTIFICATE, { input });
|
|
926
|
+
return jsonResult(data.buiIssueCertificate);
|
|
927
|
+
} catch (error) {
|
|
928
|
+
ctx.logger.error("buildmyiq certificates_issue failed", { error: String(error) });
|
|
929
|
+
return errorResult(
|
|
930
|
+
`Failed to issue certificate: ${error instanceof Error ? error.message : String(error)}`
|
|
931
|
+
);
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
);
|
|
935
|
+
server.registerTool(
|
|
936
|
+
track(mcpToolName(ctx.productName, "certificates_revoke")),
|
|
937
|
+
{
|
|
938
|
+
title: "Revoke Certificate",
|
|
939
|
+
description: "Revoke a previously issued certificate (sets status REVOKED).",
|
|
940
|
+
inputSchema: { id: z.string().describe("Certificate ID") }
|
|
941
|
+
},
|
|
942
|
+
async ({ id }) => {
|
|
943
|
+
try {
|
|
944
|
+
const data = await ctx.gql.workspace.request(REVOKE_CERTIFICATE, { id });
|
|
945
|
+
return jsonResult(data.buiRevokeCertificate);
|
|
946
|
+
} catch (error) {
|
|
947
|
+
ctx.logger.error("buildmyiq certificates_revoke failed", { error: String(error) });
|
|
948
|
+
return errorResult(
|
|
949
|
+
`Failed to revoke certificate: ${error instanceof Error ? error.message : String(error)}`
|
|
950
|
+
);
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
);
|
|
954
|
+
}
|
|
955
|
+
function registerAssessmentTools(server, ctx, track) {
|
|
956
|
+
server.registerTool(
|
|
957
|
+
track(mcpToolName(ctx.productName, "assessments_get")),
|
|
958
|
+
{
|
|
959
|
+
title: "Get Assessment",
|
|
960
|
+
description: "Fetch a single assessment by ID, including its ordered questions (with options and points).",
|
|
961
|
+
inputSchema: { id: z.string().describe("Assessment ID") }
|
|
962
|
+
},
|
|
963
|
+
async ({ id }) => {
|
|
964
|
+
try {
|
|
965
|
+
const data = await ctx.gql.workspace.request(GET_ASSESSMENT, { id });
|
|
966
|
+
if (!data.buiAssessment) return errorResult(`Assessment ${id} not found`);
|
|
967
|
+
return jsonResult(data.buiAssessment);
|
|
968
|
+
} catch (error) {
|
|
969
|
+
ctx.logger.error("buildmyiq assessments_get failed", { error: String(error) });
|
|
970
|
+
return errorResult(
|
|
971
|
+
`Failed to get assessment: ${error instanceof Error ? error.message : String(error)}`
|
|
972
|
+
);
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
);
|
|
976
|
+
server.registerTool(
|
|
977
|
+
track(mcpToolName(ctx.productName, "assessments_list")),
|
|
978
|
+
{
|
|
979
|
+
title: "List Assessments",
|
|
980
|
+
description: "List the assessments of a course, optionally filtered by status.",
|
|
981
|
+
inputSchema: {
|
|
982
|
+
courseId: z.string().describe("Course ID"),
|
|
983
|
+
status: AssessmentStatusEnum.optional().describe("Filter by assessment status")
|
|
984
|
+
}
|
|
985
|
+
},
|
|
986
|
+
async ({ courseId, status }) => {
|
|
987
|
+
try {
|
|
988
|
+
const data = await ctx.gql.workspace.request(LIST_ASSESSMENTS, { courseId, status });
|
|
989
|
+
return jsonResult(data.buiListAssessments);
|
|
990
|
+
} catch (error) {
|
|
991
|
+
ctx.logger.error("buildmyiq assessments_list failed", { error: String(error) });
|
|
992
|
+
return errorResult(
|
|
993
|
+
`Failed to list assessments: ${error instanceof Error ? error.message : String(error)}`
|
|
994
|
+
);
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
);
|
|
998
|
+
server.registerTool(
|
|
999
|
+
track(mcpToolName(ctx.productName, "assessments_create")),
|
|
1000
|
+
{
|
|
1001
|
+
title: "Create Assessment",
|
|
1002
|
+
description: "Create a draft assessment (quiz/exam/assignment/practical/survey) for a course. Add questions with questions_create, then publish with assessments_publish.",
|
|
1003
|
+
inputSchema: {
|
|
1004
|
+
courseId: z.string().min(1).describe("Owning course ID"),
|
|
1005
|
+
title: z.string().min(1).describe("Assessment title"),
|
|
1006
|
+
type: AssessmentTypeEnum.optional().describe("Assessment type (default QUIZ)"),
|
|
1007
|
+
description: z.string().optional().describe("Assessment description/instructions"),
|
|
1008
|
+
passingScore: z.number().optional().describe("Passing score threshold"),
|
|
1009
|
+
maxAttempts: z.number().int().optional().describe("Maximum allowed attempts"),
|
|
1010
|
+
timeLimitMin: z.number().int().optional().describe("Time limit in minutes"),
|
|
1011
|
+
isGraded: z.boolean().optional().describe("Whether the assessment is graded")
|
|
1012
|
+
}
|
|
1013
|
+
},
|
|
1014
|
+
async ({
|
|
1015
|
+
courseId,
|
|
1016
|
+
title,
|
|
1017
|
+
type,
|
|
1018
|
+
description,
|
|
1019
|
+
passingScore,
|
|
1020
|
+
maxAttempts,
|
|
1021
|
+
timeLimitMin,
|
|
1022
|
+
isGraded
|
|
1023
|
+
}) => {
|
|
1024
|
+
try {
|
|
1025
|
+
const input = { courseId, title };
|
|
1026
|
+
if (type !== void 0) input.type = type;
|
|
1027
|
+
if (description !== void 0) input.description = description;
|
|
1028
|
+
if (passingScore !== void 0) input.passingScore = passingScore;
|
|
1029
|
+
if (maxAttempts !== void 0) input.maxAttempts = maxAttempts;
|
|
1030
|
+
if (timeLimitMin !== void 0) input.timeLimitMin = timeLimitMin;
|
|
1031
|
+
if (isGraded !== void 0) input.isGraded = isGraded;
|
|
1032
|
+
const data = await ctx.gql.workspace.request(CREATE_ASSESSMENT, { input });
|
|
1033
|
+
return jsonResult(data.buiCreateAssessment);
|
|
1034
|
+
} catch (error) {
|
|
1035
|
+
ctx.logger.error("buildmyiq assessments_create failed", { error: String(error) });
|
|
1036
|
+
return errorResult(
|
|
1037
|
+
`Failed to create assessment: ${error instanceof Error ? error.message : String(error)}`
|
|
1038
|
+
);
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
);
|
|
1042
|
+
server.registerTool(
|
|
1043
|
+
track(mcpToolName(ctx.productName, "assessments_publish")),
|
|
1044
|
+
{
|
|
1045
|
+
title: "Publish Assessment",
|
|
1046
|
+
description: "Publish a draft assessment so learners can take it (sets status PUBLISHED).",
|
|
1047
|
+
inputSchema: { id: z.string().describe("Assessment ID") }
|
|
1048
|
+
},
|
|
1049
|
+
async ({ id }) => {
|
|
1050
|
+
try {
|
|
1051
|
+
const data = await ctx.gql.workspace.request(PUBLISH_ASSESSMENT, { id });
|
|
1052
|
+
return jsonResult(data.buiPublishAssessment);
|
|
1053
|
+
} catch (error) {
|
|
1054
|
+
ctx.logger.error("buildmyiq assessments_publish failed", { error: String(error) });
|
|
1055
|
+
return errorResult(
|
|
1056
|
+
`Failed to publish assessment: ${error instanceof Error ? error.message : String(error)}`
|
|
1057
|
+
);
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
);
|
|
1061
|
+
server.registerTool(
|
|
1062
|
+
track(mcpToolName(ctx.productName, "questions_create")),
|
|
1063
|
+
{
|
|
1064
|
+
title: "Create Question",
|
|
1065
|
+
description: "Add a question to an assessment. `options` and `answerKey` are JSON (e.g. choices array + correct-answer key); `points` weights grading.",
|
|
1066
|
+
inputSchema: {
|
|
1067
|
+
assessmentId: z.string().min(1).describe("Owning assessment ID"),
|
|
1068
|
+
prompt: z.string().min(1).describe("Question prompt/text"),
|
|
1069
|
+
type: QuestionTypeEnum.optional().describe("Question type (default SINGLE_CHOICE)"),
|
|
1070
|
+
options: JsonValue.optional().describe("Answer options JSON (choices array/object)"),
|
|
1071
|
+
answerKey: JsonValue.optional().describe("Correct-answer key JSON"),
|
|
1072
|
+
points: z.number().optional().describe("Points the question is worth"),
|
|
1073
|
+
position: z.number().int().optional().describe("Ordering position within the assessment"),
|
|
1074
|
+
explanation: z.string().optional().describe("Post-answer explanation/rationale")
|
|
1075
|
+
}
|
|
1076
|
+
},
|
|
1077
|
+
async ({ assessmentId, prompt, type, options, answerKey, points, position, explanation }) => {
|
|
1078
|
+
try {
|
|
1079
|
+
const input = { assessmentId, prompt };
|
|
1080
|
+
if (type !== void 0) input.type = type;
|
|
1081
|
+
if (options !== void 0) input.options = options;
|
|
1082
|
+
if (answerKey !== void 0) input.answerKey = answerKey;
|
|
1083
|
+
if (points !== void 0) input.points = points;
|
|
1084
|
+
if (position !== void 0) input.position = position;
|
|
1085
|
+
if (explanation !== void 0) input.explanation = explanation;
|
|
1086
|
+
const data = await ctx.gql.workspace.request(CREATE_QUESTION, { input });
|
|
1087
|
+
return jsonResult(data.buiCreateQuestion);
|
|
1088
|
+
} catch (error) {
|
|
1089
|
+
ctx.logger.error("buildmyiq questions_create failed", { error: String(error) });
|
|
1090
|
+
return errorResult(
|
|
1091
|
+
`Failed to create question: ${error instanceof Error ? error.message : String(error)}`
|
|
1092
|
+
);
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
);
|
|
1096
|
+
}
|
|
1097
|
+
function registerSubmissionTools(server, ctx, track) {
|
|
1098
|
+
server.registerTool(
|
|
1099
|
+
track(mcpToolName(ctx.productName, "submissions_list")),
|
|
1100
|
+
{
|
|
1101
|
+
title: "List Submissions",
|
|
1102
|
+
description: "List assessment submissions, optionally filtered by assessment, learner, and/or status, with cursor/limit/offset pagination. Returns { items, pageInfo }.",
|
|
1103
|
+
inputSchema: {
|
|
1104
|
+
assessmentId: z.string().optional().describe("Filter by assessment ID"),
|
|
1105
|
+
learnerId: z.string().optional().describe("Filter by learner user ID"),
|
|
1106
|
+
status: SubmissionStatusEnum.optional().describe("Filter by submission status"),
|
|
1107
|
+
limit: z.number().int().optional().describe("Page size (default 50)"),
|
|
1108
|
+
offset: z.number().int().optional().describe("Pagination offset (default 0)"),
|
|
1109
|
+
cursor: z.string().optional().describe("Opaque pagination cursor")
|
|
1110
|
+
}
|
|
1111
|
+
},
|
|
1112
|
+
async ({ assessmentId, learnerId, status, limit, offset, cursor }) => {
|
|
1113
|
+
try {
|
|
1114
|
+
const pagination = {};
|
|
1115
|
+
if (limit !== void 0) pagination.limit = limit;
|
|
1116
|
+
if (offset !== void 0) pagination.offset = offset;
|
|
1117
|
+
if (cursor !== void 0) pagination.cursor = cursor;
|
|
1118
|
+
const data = await ctx.gql.workspace.request(LIST_SUBMISSIONS, {
|
|
1119
|
+
assessmentId,
|
|
1120
|
+
learnerId,
|
|
1121
|
+
status,
|
|
1122
|
+
pagination: Object.keys(pagination).length > 0 ? pagination : void 0
|
|
1123
|
+
});
|
|
1124
|
+
return jsonResult(data.buiListSubmissions);
|
|
1125
|
+
} catch (error) {
|
|
1126
|
+
ctx.logger.error("buildmyiq submissions_list failed", { error: String(error) });
|
|
1127
|
+
return errorResult(
|
|
1128
|
+
`Failed to list submissions: ${error instanceof Error ? error.message : String(error)}`
|
|
1129
|
+
);
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
);
|
|
1133
|
+
server.registerTool(
|
|
1134
|
+
track(mcpToolName(ctx.productName, "submissions_submit")),
|
|
1135
|
+
{
|
|
1136
|
+
title: "Submit Assessment",
|
|
1137
|
+
description: "Submit a learner\u2019s answers to an assessment. `answers` is JSON keyed by question. If `learnerId` is omitted the calling actor is used; `attempt` defaults to the next attempt number.",
|
|
1138
|
+
inputSchema: {
|
|
1139
|
+
assessmentId: z.string().min(1).describe("Assessment being submitted"),
|
|
1140
|
+
answers: JsonValue.optional().describe("Answers JSON (keyed by question)"),
|
|
1141
|
+
learnerId: z.string().optional().describe("Learner user ID (defaults to the caller)"),
|
|
1142
|
+
enrollmentId: z.string().optional().describe("Source enrollment ID"),
|
|
1143
|
+
attempt: z.number().int().optional().describe("Attempt number")
|
|
1144
|
+
}
|
|
1145
|
+
},
|
|
1146
|
+
async ({ assessmentId, answers, learnerId, enrollmentId, attempt }) => {
|
|
1147
|
+
try {
|
|
1148
|
+
const input = { assessmentId };
|
|
1149
|
+
if (answers !== void 0) input.answers = answers;
|
|
1150
|
+
if (learnerId !== void 0) input.learnerId = learnerId;
|
|
1151
|
+
if (enrollmentId !== void 0) input.enrollmentId = enrollmentId;
|
|
1152
|
+
if (attempt !== void 0) input.attempt = attempt;
|
|
1153
|
+
const data = await ctx.gql.workspace.request(SUBMIT_ASSESSMENT, { input });
|
|
1154
|
+
return jsonResult(data.buiSubmitAssessment);
|
|
1155
|
+
} catch (error) {
|
|
1156
|
+
ctx.logger.error("buildmyiq submissions_submit failed", { error: String(error) });
|
|
1157
|
+
return errorResult(
|
|
1158
|
+
`Failed to submit assessment: ${error instanceof Error ? error.message : String(error)}`
|
|
1159
|
+
);
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
);
|
|
1163
|
+
server.registerTool(
|
|
1164
|
+
track(mcpToolName(ctx.productName, "submissions_grade")),
|
|
1165
|
+
{
|
|
1166
|
+
title: "Grade Submission",
|
|
1167
|
+
description: "Grade a submission: record a score, an optional pass/fail flag, and optional feedback. Moves the submission to GRADED.",
|
|
1168
|
+
inputSchema: {
|
|
1169
|
+
id: z.string().describe("Submission ID"),
|
|
1170
|
+
score: z.number().describe("Awarded score"),
|
|
1171
|
+
passed: z.boolean().optional().describe("Whether the learner passed"),
|
|
1172
|
+
feedback: z.string().optional().describe("Grader feedback")
|
|
1173
|
+
}
|
|
1174
|
+
},
|
|
1175
|
+
async ({ id, score, passed, feedback }) => {
|
|
1176
|
+
try {
|
|
1177
|
+
const input = { score };
|
|
1178
|
+
if (passed !== void 0) input.passed = passed;
|
|
1179
|
+
if (feedback !== void 0) input.feedback = feedback;
|
|
1180
|
+
const data = await ctx.gql.workspace.request(GRADE_SUBMISSION, { id, input });
|
|
1181
|
+
return jsonResult(data.buiGradeSubmission);
|
|
1182
|
+
} catch (error) {
|
|
1183
|
+
ctx.logger.error("buildmyiq submissions_grade failed", { error: String(error) });
|
|
1184
|
+
return errorResult(
|
|
1185
|
+
`Failed to grade submission: ${error instanceof Error ? error.message : String(error)}`
|
|
1186
|
+
);
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
);
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
// src/modules/buildmyiq/tools/health.ts
|
|
1193
|
+
function registerHealthTools(server, ctx, track) {
|
|
1194
|
+
server.registerTool(
|
|
1195
|
+
track(mcpToolName(ctx.productName, "health")),
|
|
1196
|
+
{
|
|
1197
|
+
title: "BuildMyIQ Health Check",
|
|
1198
|
+
description: "Public liveness/identity probe for the BuildMyIQ subgraph (buiHealth). Requires no authentication.",
|
|
1199
|
+
inputSchema: {}
|
|
1200
|
+
},
|
|
1201
|
+
async () => {
|
|
1202
|
+
try {
|
|
1203
|
+
const data = await ctx.gql.workspace.request(HEALTH, {});
|
|
1204
|
+
return jsonResult({ ok: true, status: data.buiHealth });
|
|
1205
|
+
} catch (error) {
|
|
1206
|
+
ctx.logger.error("buildmyiq health failed", { error: String(error) });
|
|
1207
|
+
return errorResult(
|
|
1208
|
+
`BuildMyIQ health check failed: ${error instanceof Error ? error.message : String(error)}`
|
|
1209
|
+
);
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
);
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
// src/modules/buildmyiq/resources.ts
|
|
1216
|
+
function registerBuildmyiqResources(server, ctx, track) {
|
|
1217
|
+
const schemaUri = track(mcpResourceUri(ctx.productName, "schema"));
|
|
1218
|
+
const coursesUri = track(mcpResourceUri(ctx.productName, "courses"));
|
|
1219
|
+
const skillsUri = track(mcpResourceUri(ctx.productName, "skills"));
|
|
1220
|
+
const healthUri = track(mcpResourceUri(ctx.productName, "health"));
|
|
1221
|
+
server.registerResource(
|
|
1222
|
+
"buildmyiq-schema",
|
|
1223
|
+
schemaUri,
|
|
1224
|
+
{
|
|
1225
|
+
title: "BuildMyIQ Domain Schema",
|
|
1226
|
+
description: "Overview of the BuildMyIQ learning-platform domain \u2014 entities, enums, and the per-entity tool surface.",
|
|
1227
|
+
mimeType: "text/plain"
|
|
1228
|
+
},
|
|
1229
|
+
async () => {
|
|
1230
|
+
ctx.logger.debug(`Resource ${schemaUri} requested`);
|
|
1231
|
+
const schema = `# BuildMyIQ Domain (Overview)
|
|
1232
|
+
|
|
1233
|
+
AI-native Education, Learning & Development platform \u2014 courses, learning,
|
|
1234
|
+
and assessment for schools, universities, and teams.
|
|
1235
|
+
|
|
1236
|
+
## Entities
|
|
1237
|
+
- BuiCourse (+BuiCourseConnection) \u2014 a course (modules, lessons, skill, cover File)
|
|
1238
|
+
- BuiModule \u2014 a structural section of a course (has lessons)
|
|
1239
|
+
- BuiLesson \u2014 a learning unit (ARTICLE/VIDEO/QUIZ/... , media File)
|
|
1240
|
+
- BuiSkill \u2014 a competency a course maps to
|
|
1241
|
+
- BuiEnrollment (+BuiEnrollmentConnection) \u2014 a learner's enrollment (cohort, progress)
|
|
1242
|
+
- BuiLessonProgress \u2014 per-lesson progress within an enrollment
|
|
1243
|
+
- BuiCohort \u2014 a scheduled, facilitated group of a course
|
|
1244
|
+
- BuiCertificate \u2014 a completion certificate (rendered PDF File)
|
|
1245
|
+
- BuiAssessment \u2014 a quiz/exam/assignment (has questions)
|
|
1246
|
+
- BuiQuestion \u2014 a question within an assessment
|
|
1247
|
+
- BuiSubmission (+BuiSubmissionConnection) \u2014 a learner attempt + grading
|
|
1248
|
+
|
|
1249
|
+
## Enums
|
|
1250
|
+
- BuiCourseLevel: BEGINNER, INTERMEDIATE, ADVANCED, EXPERT
|
|
1251
|
+
- BuiCourseStatus: DRAFT, IN_REVIEW, PUBLISHED, ARCHIVED
|
|
1252
|
+
- BuiSkillCategory: BUSINESS, COGNITIVE, CREATIVE, LANGUAGE, SOCIAL, TECHNICAL
|
|
1253
|
+
- BuiLessonType: ARTICLE, ASSIGNMENT, INTERACTIVE, LIVE_SESSION, QUIZ, VIDEO
|
|
1254
|
+
- BuiEnrollmentStatus: ENROLLED, IN_PROGRESS, COMPLETED, DROPPED, EXPIRED
|
|
1255
|
+
- BuiProgressStatus: NOT_STARTED, IN_PROGRESS, COMPLETED
|
|
1256
|
+
- BuiCohortStatus: SCHEDULED, ENROLLING, ACTIVE, COMPLETED, CANCELLED
|
|
1257
|
+
- BuiAssessmentType: ASSIGNMENT, EXAM, PRACTICAL, QUIZ, SURVEY
|
|
1258
|
+
- BuiAssessmentStatus: DRAFT, PUBLISHED, ARCHIVED
|
|
1259
|
+
- BuiQuestionType: CODE, ESSAY, MULTIPLE_CHOICE, SHORT_ANSWER, SINGLE_CHOICE, TRUE_FALSE
|
|
1260
|
+
- BuiSubmissionStatus: IN_PROGRESS, SUBMITTED, GRADING, GRADED, RETURNED
|
|
1261
|
+
|
|
1262
|
+
## Tool groups
|
|
1263
|
+
courses, modules, lessons, skills, enrollments, cohorts, certificates,
|
|
1264
|
+
assessments (+questions), submissions, health.
|
|
1265
|
+
All run against the WORKSPACE gateway (workspace-scoped) except the public
|
|
1266
|
+
buiHealth probe.
|
|
1267
|
+
`;
|
|
1268
|
+
return {
|
|
1269
|
+
contents: [{ uri: schemaUri, text: schema, mimeType: "text/plain" }]
|
|
1270
|
+
};
|
|
1271
|
+
}
|
|
1272
|
+
);
|
|
1273
|
+
server.registerResource(
|
|
1274
|
+
"buildmyiq-courses",
|
|
1275
|
+
coursesUri,
|
|
1276
|
+
{
|
|
1277
|
+
title: "BuildMyIQ Course Catalog",
|
|
1278
|
+
description: "The first 50 courses in the bound workspace.",
|
|
1279
|
+
mimeType: "application/json"
|
|
1280
|
+
},
|
|
1281
|
+
async () => {
|
|
1282
|
+
ctx.logger.debug(`Resource ${coursesUri} requested`);
|
|
1283
|
+
try {
|
|
1284
|
+
const data = await ctx.gql.workspace.request(LIST_COURSES, {
|
|
1285
|
+
pagination: { limit: 50, offset: 0 }
|
|
1286
|
+
});
|
|
1287
|
+
return {
|
|
1288
|
+
contents: [
|
|
1289
|
+
{
|
|
1290
|
+
uri: coursesUri,
|
|
1291
|
+
text: JSON.stringify(data.buiListCourses, null, 2),
|
|
1292
|
+
mimeType: "application/json"
|
|
1293
|
+
}
|
|
1294
|
+
]
|
|
1295
|
+
};
|
|
1296
|
+
} catch (error) {
|
|
1297
|
+
return {
|
|
1298
|
+
contents: [
|
|
1299
|
+
{
|
|
1300
|
+
uri: coursesUri,
|
|
1301
|
+
text: JSON.stringify({
|
|
1302
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1303
|
+
}),
|
|
1304
|
+
mimeType: "application/json"
|
|
1305
|
+
}
|
|
1306
|
+
]
|
|
1307
|
+
};
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
);
|
|
1311
|
+
server.registerResource(
|
|
1312
|
+
"buildmyiq-skills",
|
|
1313
|
+
skillsUri,
|
|
1314
|
+
{
|
|
1315
|
+
title: "BuildMyIQ Skill Taxonomy",
|
|
1316
|
+
description: "The skills/competencies defined in the bound workspace.",
|
|
1317
|
+
mimeType: "application/json"
|
|
1318
|
+
},
|
|
1319
|
+
async () => {
|
|
1320
|
+
ctx.logger.debug(`Resource ${skillsUri} requested`);
|
|
1321
|
+
try {
|
|
1322
|
+
const data = await ctx.gql.workspace.request(LIST_SKILLS, {});
|
|
1323
|
+
return {
|
|
1324
|
+
contents: [
|
|
1325
|
+
{
|
|
1326
|
+
uri: skillsUri,
|
|
1327
|
+
text: JSON.stringify(data.buiListSkills, null, 2),
|
|
1328
|
+
mimeType: "application/json"
|
|
1329
|
+
}
|
|
1330
|
+
]
|
|
1331
|
+
};
|
|
1332
|
+
} catch (error) {
|
|
1333
|
+
return {
|
|
1334
|
+
contents: [
|
|
1335
|
+
{
|
|
1336
|
+
uri: skillsUri,
|
|
1337
|
+
text: JSON.stringify({
|
|
1338
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1339
|
+
}),
|
|
1340
|
+
mimeType: "application/json"
|
|
1341
|
+
}
|
|
1342
|
+
]
|
|
1343
|
+
};
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
);
|
|
1347
|
+
server.registerResource(
|
|
1348
|
+
"buildmyiq-health",
|
|
1349
|
+
healthUri,
|
|
1350
|
+
{
|
|
1351
|
+
title: "BuildMyIQ Health",
|
|
1352
|
+
description: "Live liveness/identity probe for the BuildMyIQ subgraph (buiHealth).",
|
|
1353
|
+
mimeType: "application/json"
|
|
1354
|
+
},
|
|
1355
|
+
async () => {
|
|
1356
|
+
ctx.logger.debug(`Resource ${healthUri} requested`);
|
|
1357
|
+
try {
|
|
1358
|
+
const data = await ctx.gql.workspace.request(HEALTH, {});
|
|
1359
|
+
return {
|
|
1360
|
+
contents: [
|
|
1361
|
+
{
|
|
1362
|
+
uri: healthUri,
|
|
1363
|
+
text: JSON.stringify({ ok: true, status: data.buiHealth }, null, 2),
|
|
1364
|
+
mimeType: "application/json"
|
|
1365
|
+
}
|
|
1366
|
+
]
|
|
1367
|
+
};
|
|
1368
|
+
} catch (error) {
|
|
1369
|
+
return {
|
|
1370
|
+
contents: [
|
|
1371
|
+
{
|
|
1372
|
+
uri: healthUri,
|
|
1373
|
+
text: JSON.stringify({
|
|
1374
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1375
|
+
}),
|
|
1376
|
+
mimeType: "application/json"
|
|
1377
|
+
}
|
|
1378
|
+
]
|
|
1379
|
+
};
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
);
|
|
1383
|
+
}
|
|
1384
|
+
function registerBuildmyiqPrompts(server, ctx, track) {
|
|
1385
|
+
const p = ctx.productName;
|
|
1386
|
+
const authoringName = track(mcpPromptName(p, "course-authoring"));
|
|
1387
|
+
const opsName = track(mcpPromptName(p, "cohort-operations"));
|
|
1388
|
+
server.registerPrompt(
|
|
1389
|
+
authoringName,
|
|
1390
|
+
{
|
|
1391
|
+
title: "Course Authoring",
|
|
1392
|
+
description: "Guide for authoring a BuildMyIQ course end to end: create the course, structure it into modules and lessons, add an assessment with questions, and publish.",
|
|
1393
|
+
argsSchema: {
|
|
1394
|
+
title: z.string().optional().describe("Course title"),
|
|
1395
|
+
level: z.string().optional().describe("Course level (BEGINNER/INTERMEDIATE/ADVANCED/EXPERT)")
|
|
1396
|
+
}
|
|
1397
|
+
},
|
|
1398
|
+
({ title, level }) => {
|
|
1399
|
+
ctx.logger.debug(`Prompt ${authoringName} called`, { title, level });
|
|
1400
|
+
const titleNote = title ? `Use the title "${title}".` : "Ask the user for the course title.";
|
|
1401
|
+
const levelNote = level ? `Use level ${level}.` : "Help the user pick a BuiCourseLevel.";
|
|
1402
|
+
return {
|
|
1403
|
+
messages: [
|
|
1404
|
+
{
|
|
1405
|
+
role: "user",
|
|
1406
|
+
content: {
|
|
1407
|
+
type: "text",
|
|
1408
|
+
text: `Help me author a new BuildMyIQ course.
|
|
1409
|
+
|
|
1410
|
+
1. **Create Course**: Use ${p}_courses_create. ${titleNote} ${levelNote} Provide a unique slug; optionally link a skill (${p}_skills_list / ${p}_skills_create) and a cover File.
|
|
1411
|
+
2. **Structure**: Use ${p}_modules_create to add sections, then ${p}_lessons_create for each lesson (set type=ARTICLE/VIDEO/QUIZ/... and position).
|
|
1412
|
+
3. **Assess**: Use ${p}_assessments_create to add a quiz/exam, then ${p}_questions_create for each question. Publish it with ${p}_assessments_publish.
|
|
1413
|
+
4. **Review Structure**: Use ${p}_courses_get to verify modules + lessons, and ${p}_assessments_list for the course.
|
|
1414
|
+
5. **Publish**: Use ${p}_courses_publish to move the course through DRAFT \u2192 IN_REVIEW \u2192 PUBLISHED.
|
|
1415
|
+
|
|
1416
|
+
Show results at each step and confirm before publishing.`
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
]
|
|
1420
|
+
};
|
|
1421
|
+
}
|
|
1422
|
+
);
|
|
1423
|
+
server.registerPrompt(
|
|
1424
|
+
opsName,
|
|
1425
|
+
{
|
|
1426
|
+
title: "Cohort Operations",
|
|
1427
|
+
description: "Operations workflow for running a course cohort: enroll learners, track progress, grade submissions, and issue certificates.",
|
|
1428
|
+
argsSchema: {
|
|
1429
|
+
courseId: z.string().optional().describe("Course ID to operate")
|
|
1430
|
+
}
|
|
1431
|
+
},
|
|
1432
|
+
({ courseId }) => {
|
|
1433
|
+
ctx.logger.debug(`Prompt ${opsName} called`, { courseId });
|
|
1434
|
+
const courseNote = courseId ? `Operate course "${courseId}".` : `First list courses with ${p}_courses_list and pick one.`;
|
|
1435
|
+
return {
|
|
1436
|
+
messages: [
|
|
1437
|
+
{
|
|
1438
|
+
role: "user",
|
|
1439
|
+
content: {
|
|
1440
|
+
type: "text",
|
|
1441
|
+
text: `Run BuildMyIQ cohort operations. ${courseNote}
|
|
1442
|
+
|
|
1443
|
+
1. **Cohort**: Use ${p}_cohorts_list (or ${p}_cohorts_create) to find/open a cohort for the course.
|
|
1444
|
+
2. **Enroll**: Use ${p}_enrollments_enroll for each learner (optionally within the cohort).
|
|
1445
|
+
3. **Progress**: Use ${p}_enrollments_list filtered by courseId/status to see who is IN_PROGRESS vs COMPLETED; drill in with ${p}_enrollments_get.
|
|
1446
|
+
4. **Grade**: Use ${p}_submissions_list filtered by status=SUBMITTED, then ${p}_submissions_grade (score, passed, feedback) for each.
|
|
1447
|
+
5. **Advance**: Use ${p}_enrollments_transition to mark finished learners COMPLETED.
|
|
1448
|
+
6. **Certify**: For passing learners use ${p}_certificates_issue (unique serial), and ${p}_certificates_list to confirm.
|
|
1449
|
+
|
|
1450
|
+
Summarize what needs attention after each step.`
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
]
|
|
1454
|
+
};
|
|
1455
|
+
}
|
|
1456
|
+
);
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
// src/modules/buildmyiq/index.ts
|
|
1460
|
+
var buildmyiqModule = {
|
|
1461
|
+
id: "buildmyiq",
|
|
1462
|
+
version: "0.0.1",
|
|
1463
|
+
displayName: "BuildMyIQ",
|
|
1464
|
+
dependsOn: ["auth"],
|
|
1465
|
+
capabilities: { tools: {}, resources: {}, prompts: {} },
|
|
1466
|
+
register(server, ctx) {
|
|
1467
|
+
const tools = [];
|
|
1468
|
+
const resources = [];
|
|
1469
|
+
const prompts = [];
|
|
1470
|
+
const trackTool = (n) => {
|
|
1471
|
+
tools.push(n);
|
|
1472
|
+
return n;
|
|
1473
|
+
};
|
|
1474
|
+
const trackResource = (uri) => {
|
|
1475
|
+
resources.push(uri);
|
|
1476
|
+
return uri;
|
|
1477
|
+
};
|
|
1478
|
+
const trackPrompt = (n) => {
|
|
1479
|
+
prompts.push(n);
|
|
1480
|
+
return n;
|
|
1481
|
+
};
|
|
1482
|
+
registerCourseTools(server, ctx, trackTool);
|
|
1483
|
+
registerModuleTools(server, ctx, trackTool);
|
|
1484
|
+
registerLessonTools(server, ctx, trackTool);
|
|
1485
|
+
registerSkillTools(server, ctx, trackTool);
|
|
1486
|
+
registerEnrollmentTools(server, ctx, trackTool);
|
|
1487
|
+
registerCohortTools(server, ctx, trackTool);
|
|
1488
|
+
registerCertificateTools(server, ctx, trackTool);
|
|
1489
|
+
registerAssessmentTools(server, ctx, trackTool);
|
|
1490
|
+
registerSubmissionTools(server, ctx, trackTool);
|
|
1491
|
+
registerHealthTools(server, ctx, trackTool);
|
|
1492
|
+
registerBuildmyiqResources(server, ctx, trackResource);
|
|
1493
|
+
registerBuildmyiqPrompts(server, ctx, trackPrompt);
|
|
1494
|
+
return { tools, resources, prompts };
|
|
1495
|
+
}
|
|
1496
|
+
};
|
|
1497
|
+
var buildmyiq_default = buildmyiqModule;
|
|
1498
|
+
|
|
1499
|
+
export { buildmyiq_default as default };
|
|
1500
|
+
//# sourceMappingURL=index.js.map
|
|
1501
|
+
//# sourceMappingURL=index.js.map
|