@classroomio/mcp 0.0.2
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/LICENSE +661 -0
- package/README.md +273 -0
- package/dist/index.js +830 -0
- package/package.json +47 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,830 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6
|
+
|
|
7
|
+
// src/api-client.ts
|
|
8
|
+
var ClassroomIoApiError = class extends Error {
|
|
9
|
+
constructor(message, status, code, field) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.status = status;
|
|
12
|
+
this.code = code;
|
|
13
|
+
this.field = field;
|
|
14
|
+
this.name = "ClassroomIoApiError";
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
var ClassroomIoApiClient = class {
|
|
18
|
+
constructor(config) {
|
|
19
|
+
this.config = config;
|
|
20
|
+
}
|
|
21
|
+
async createCourseDraft(payload) {
|
|
22
|
+
return this.request("/organization/course-import/drafts", {
|
|
23
|
+
method: "POST",
|
|
24
|
+
body: payload
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
async createCourseDraftFromCourse(payload) {
|
|
28
|
+
return this.request("/organization/course-import/drafts/from-course", {
|
|
29
|
+
method: "POST",
|
|
30
|
+
body: payload
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
async getCourseStructure(courseId) {
|
|
34
|
+
return this.request(`/organization/course-import/courses/${courseId}/structure`, {
|
|
35
|
+
method: "GET"
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
async getCourseDraft(draftId) {
|
|
39
|
+
return this.request(`/organization/course-import/drafts/${draftId}`, {
|
|
40
|
+
method: "GET"
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
async updateCourseDraft(draftId, payload) {
|
|
44
|
+
return this.request(`/organization/course-import/drafts/${draftId}`, {
|
|
45
|
+
method: "PUT",
|
|
46
|
+
body: payload
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
async publishCourseDraft(draftId, payload) {
|
|
50
|
+
return this.request(`/organization/course-import/drafts/${draftId}/publish`, {
|
|
51
|
+
method: "POST",
|
|
52
|
+
body: payload
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
async publishCourseDraftToExistingCourse(draftId, payload) {
|
|
56
|
+
return this.request(`/organization/course-import/drafts/${draftId}/publish-existing-course`, {
|
|
57
|
+
method: "POST",
|
|
58
|
+
body: payload
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
async request(path, options) {
|
|
62
|
+
const response = await fetch(new URL(path, this.config.CLASSROOMIO_API_URL), {
|
|
63
|
+
method: options.method,
|
|
64
|
+
headers: {
|
|
65
|
+
Authorization: `Bearer ${this.config.CLASSROOMIO_API_KEY}`,
|
|
66
|
+
"content-type": "application/json",
|
|
67
|
+
"user-agent": this.config.CLASSROOMIO_USER_AGENT
|
|
68
|
+
},
|
|
69
|
+
body: options.body ? JSON.stringify(options.body) : void 0
|
|
70
|
+
});
|
|
71
|
+
const json = await response.json().catch(() => null);
|
|
72
|
+
if (!response.ok) {
|
|
73
|
+
const errorPayload = json;
|
|
74
|
+
throw new ClassroomIoApiError(
|
|
75
|
+
errorPayload?.error ?? errorPayload?.message ?? `ClassroomIO request failed with status ${response.status}`,
|
|
76
|
+
response.status,
|
|
77
|
+
errorPayload?.code,
|
|
78
|
+
errorPayload?.field
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
if (!json || typeof json !== "object" || !("success" in json) || !json.success) {
|
|
82
|
+
throw new ClassroomIoApiError("ClassroomIO returned an invalid response payload", response.status);
|
|
83
|
+
}
|
|
84
|
+
return json.data;
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
// src/config.ts
|
|
89
|
+
import { z } from "zod";
|
|
90
|
+
var ZConfig = z.object({
|
|
91
|
+
CLASSROOMIO_API_URL: z.url().default("https://api.classroomio.com"),
|
|
92
|
+
CLASSROOMIO_API_KEY: z.string().min(1),
|
|
93
|
+
CLASSROOMIO_USER_AGENT: z.string().min(1).default("classroomio-mcp/0.0.1")
|
|
94
|
+
});
|
|
95
|
+
function getConfig(env = process.env) {
|
|
96
|
+
return ZConfig.parse(env);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ../utils/dist/validation/course-import/course-import.js
|
|
100
|
+
import * as z3 from "zod";
|
|
101
|
+
|
|
102
|
+
// ../utils/dist/validation/course/course.js
|
|
103
|
+
import * as z2 from "zod";
|
|
104
|
+
|
|
105
|
+
// ../question-types/dist/question-type-keys.js
|
|
106
|
+
var QUESTION_TYPE_KEY = {
|
|
107
|
+
RADIO: "RADIO",
|
|
108
|
+
CHECKBOX: "CHECKBOX",
|
|
109
|
+
TEXTAREA: "TEXTAREA",
|
|
110
|
+
TRUE_FALSE: "TRUE_FALSE",
|
|
111
|
+
SHORT_ANSWER: "SHORT_ANSWER",
|
|
112
|
+
NUMERIC: "NUMERIC",
|
|
113
|
+
FILL_BLANK: "FILL_BLANK",
|
|
114
|
+
FILE_UPLOAD: "FILE_UPLOAD",
|
|
115
|
+
MATCHING: "MATCHING",
|
|
116
|
+
ORDERING: "ORDERING",
|
|
117
|
+
HOTSPOT: "HOTSPOT",
|
|
118
|
+
LINK: "LINK"
|
|
119
|
+
};
|
|
120
|
+
var LEGACY_QUESTION_TYPE_KEYS = [
|
|
121
|
+
QUESTION_TYPE_KEY.RADIO,
|
|
122
|
+
QUESTION_TYPE_KEY.CHECKBOX,
|
|
123
|
+
QUESTION_TYPE_KEY.TEXTAREA
|
|
124
|
+
];
|
|
125
|
+
|
|
126
|
+
// ../question-types/dist/question-type-registry.js
|
|
127
|
+
var QUESTION_TYPE_REGISTRY = [
|
|
128
|
+
{
|
|
129
|
+
key: QUESTION_TYPE_KEY.RADIO,
|
|
130
|
+
typename: "RADIO",
|
|
131
|
+
label: "Single answer",
|
|
132
|
+
id: 1,
|
|
133
|
+
autoGradable: true,
|
|
134
|
+
supportsPartialCredit: false,
|
|
135
|
+
manualGradingRequired: false
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
key: QUESTION_TYPE_KEY.CHECKBOX,
|
|
139
|
+
typename: "CHECKBOX",
|
|
140
|
+
label: "Multiple answers",
|
|
141
|
+
id: 2,
|
|
142
|
+
autoGradable: true,
|
|
143
|
+
supportsPartialCredit: true,
|
|
144
|
+
manualGradingRequired: false
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
key: QUESTION_TYPE_KEY.TEXTAREA,
|
|
148
|
+
typename: "TEXTAREA",
|
|
149
|
+
label: "Paragraph",
|
|
150
|
+
id: 3,
|
|
151
|
+
autoGradable: false,
|
|
152
|
+
supportsPartialCredit: false,
|
|
153
|
+
manualGradingRequired: true
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
key: QUESTION_TYPE_KEY.TRUE_FALSE,
|
|
157
|
+
typename: "TRUE_FALSE",
|
|
158
|
+
label: "True/False",
|
|
159
|
+
id: 4,
|
|
160
|
+
autoGradable: true,
|
|
161
|
+
supportsPartialCredit: false,
|
|
162
|
+
manualGradingRequired: false
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
key: QUESTION_TYPE_KEY.SHORT_ANSWER,
|
|
166
|
+
typename: "SHORT_ANSWER",
|
|
167
|
+
label: "Short answer",
|
|
168
|
+
id: 5,
|
|
169
|
+
autoGradable: false,
|
|
170
|
+
supportsPartialCredit: false,
|
|
171
|
+
manualGradingRequired: true
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
key: QUESTION_TYPE_KEY.NUMERIC,
|
|
175
|
+
typename: "NUMERIC",
|
|
176
|
+
label: "Numeric answer",
|
|
177
|
+
id: 6,
|
|
178
|
+
autoGradable: true,
|
|
179
|
+
supportsPartialCredit: true,
|
|
180
|
+
manualGradingRequired: false
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
key: QUESTION_TYPE_KEY.FILL_BLANK,
|
|
184
|
+
typename: "FILL_BLANK",
|
|
185
|
+
label: "Fill in the blank",
|
|
186
|
+
id: 7,
|
|
187
|
+
autoGradable: true,
|
|
188
|
+
supportsPartialCredit: true,
|
|
189
|
+
manualGradingRequired: false
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
key: QUESTION_TYPE_KEY.FILE_UPLOAD,
|
|
193
|
+
typename: "FILE_UPLOAD",
|
|
194
|
+
label: "File upload",
|
|
195
|
+
id: 8,
|
|
196
|
+
autoGradable: false,
|
|
197
|
+
supportsPartialCredit: false,
|
|
198
|
+
manualGradingRequired: true
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
key: QUESTION_TYPE_KEY.MATCHING,
|
|
202
|
+
typename: "MATCHING",
|
|
203
|
+
label: "Matching",
|
|
204
|
+
id: 9,
|
|
205
|
+
autoGradable: true,
|
|
206
|
+
supportsPartialCredit: true,
|
|
207
|
+
manualGradingRequired: false
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
key: QUESTION_TYPE_KEY.ORDERING,
|
|
211
|
+
typename: "ORDERING",
|
|
212
|
+
label: "Ordering",
|
|
213
|
+
id: 10,
|
|
214
|
+
autoGradable: true,
|
|
215
|
+
supportsPartialCredit: true,
|
|
216
|
+
manualGradingRequired: false
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
key: QUESTION_TYPE_KEY.HOTSPOT,
|
|
220
|
+
typename: "HOTSPOT",
|
|
221
|
+
label: "Hotspot",
|
|
222
|
+
id: 11,
|
|
223
|
+
autoGradable: true,
|
|
224
|
+
supportsPartialCredit: true,
|
|
225
|
+
manualGradingRequired: false
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
key: QUESTION_TYPE_KEY.LINK,
|
|
229
|
+
typename: "LINK",
|
|
230
|
+
label: "Links",
|
|
231
|
+
id: 12,
|
|
232
|
+
autoGradable: false,
|
|
233
|
+
supportsPartialCredit: false,
|
|
234
|
+
manualGradingRequired: true
|
|
235
|
+
}
|
|
236
|
+
];
|
|
237
|
+
var QUESTION_TYPE_BY_KEY = Object.freeze(QUESTION_TYPE_REGISTRY.reduce((acc, type) => {
|
|
238
|
+
acc[type.key] = type;
|
|
239
|
+
return acc;
|
|
240
|
+
}, {}));
|
|
241
|
+
var QUESTION_TYPE_IDS = Object.freeze(QUESTION_TYPE_REGISTRY.reduce((acc, type) => {
|
|
242
|
+
acc[type.key] = type.id;
|
|
243
|
+
return acc;
|
|
244
|
+
}, {}));
|
|
245
|
+
var QUESTION_TYPE_TYPENAME_TO_KEY = Object.freeze(QUESTION_TYPE_REGISTRY.reduce((acc, type) => {
|
|
246
|
+
acc[type.typename] = type.key;
|
|
247
|
+
return acc;
|
|
248
|
+
}, {}));
|
|
249
|
+
var QUESTION_TYPE_ID_TO_KEY = Object.freeze(QUESTION_TYPE_REGISTRY.reduce((acc, type) => {
|
|
250
|
+
acc[type.id] = type.key;
|
|
251
|
+
return acc;
|
|
252
|
+
}, {}));
|
|
253
|
+
|
|
254
|
+
// ../question-types/dist/answer-codecs.js
|
|
255
|
+
var TRUE_LABELS = /* @__PURE__ */ new Set(["true", "1", "yes"]);
|
|
256
|
+
var FALSE_LABELS = /* @__PURE__ */ new Set(["false", "0", "no"]);
|
|
257
|
+
function labelToBoolean(label) {
|
|
258
|
+
const normalized = label.trim().toLowerCase();
|
|
259
|
+
if (TRUE_LABELS.has(normalized))
|
|
260
|
+
return true;
|
|
261
|
+
if (FALSE_LABELS.has(normalized))
|
|
262
|
+
return false;
|
|
263
|
+
return void 0;
|
|
264
|
+
}
|
|
265
|
+
var RADIO_CODEC = {
|
|
266
|
+
type: "RADIO",
|
|
267
|
+
toApiPayload(data, questionId) {
|
|
268
|
+
return { questionId, optionId: data.optionId };
|
|
269
|
+
},
|
|
270
|
+
fromApiPayload(payload) {
|
|
271
|
+
const optionId = payload.optionId;
|
|
272
|
+
if (optionId === void 0 || Number.isNaN(optionId))
|
|
273
|
+
return null;
|
|
274
|
+
return { type: "RADIO", optionId };
|
|
275
|
+
}
|
|
276
|
+
};
|
|
277
|
+
var CHECKBOX_CODEC = {
|
|
278
|
+
type: "CHECKBOX",
|
|
279
|
+
toApiPayload(data, questionId) {
|
|
280
|
+
return {
|
|
281
|
+
questionId,
|
|
282
|
+
answer: JSON.stringify({ type: "CHECKBOX", optionIds: data.optionIds })
|
|
283
|
+
};
|
|
284
|
+
},
|
|
285
|
+
fromApiPayload(payload) {
|
|
286
|
+
if (!payload.answer)
|
|
287
|
+
return null;
|
|
288
|
+
const { optionIds } = JSON.parse(payload.answer);
|
|
289
|
+
if (!Array.isArray(optionIds))
|
|
290
|
+
return null;
|
|
291
|
+
return { type: "CHECKBOX", optionIds };
|
|
292
|
+
}
|
|
293
|
+
};
|
|
294
|
+
var TRUE_FALSE_CODEC = {
|
|
295
|
+
type: "TRUE_FALSE",
|
|
296
|
+
toApiPayload(data, questionId) {
|
|
297
|
+
return { questionId, answer: String(data.value) };
|
|
298
|
+
},
|
|
299
|
+
fromApiPayload(payload) {
|
|
300
|
+
if (payload.answer === void 0)
|
|
301
|
+
return null;
|
|
302
|
+
const resolved = labelToBoolean(payload.answer);
|
|
303
|
+
if (resolved === void 0)
|
|
304
|
+
return null;
|
|
305
|
+
return { type: "TRUE_FALSE", value: resolved };
|
|
306
|
+
}
|
|
307
|
+
};
|
|
308
|
+
var TEXTAREA_CODEC = {
|
|
309
|
+
type: "TEXTAREA",
|
|
310
|
+
toApiPayload(data, questionId) {
|
|
311
|
+
return { questionId, answer: data.text };
|
|
312
|
+
},
|
|
313
|
+
fromApiPayload(payload) {
|
|
314
|
+
if (payload.answer === void 0)
|
|
315
|
+
return null;
|
|
316
|
+
return { type: "TEXTAREA", text: payload.answer };
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
var SHORT_ANSWER_CODEC = {
|
|
320
|
+
type: "SHORT_ANSWER",
|
|
321
|
+
toApiPayload(data, questionId) {
|
|
322
|
+
return { questionId, answer: data.text };
|
|
323
|
+
},
|
|
324
|
+
fromApiPayload(payload) {
|
|
325
|
+
if (payload.answer === void 0)
|
|
326
|
+
return null;
|
|
327
|
+
return { type: "SHORT_ANSWER", text: payload.answer };
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
var NUMERIC_CODEC = {
|
|
331
|
+
type: "NUMERIC",
|
|
332
|
+
toApiPayload(data, questionId) {
|
|
333
|
+
return { questionId, answer: JSON.stringify({ type: "NUMERIC", value: data.value }) };
|
|
334
|
+
},
|
|
335
|
+
fromApiPayload(payload) {
|
|
336
|
+
if (!payload.answer)
|
|
337
|
+
return null;
|
|
338
|
+
const { value } = JSON.parse(payload.answer);
|
|
339
|
+
return { type: "NUMERIC", value };
|
|
340
|
+
}
|
|
341
|
+
};
|
|
342
|
+
var FILL_BLANK_CODEC = {
|
|
343
|
+
type: "FILL_BLANK",
|
|
344
|
+
toApiPayload(data, questionId) {
|
|
345
|
+
return { questionId, answer: JSON.stringify({ type: "FILL_BLANK", answers: data.values }) };
|
|
346
|
+
},
|
|
347
|
+
fromApiPayload(payload) {
|
|
348
|
+
if (!payload.answer)
|
|
349
|
+
return null;
|
|
350
|
+
const { answers } = JSON.parse(payload.answer);
|
|
351
|
+
const values = answers.map((v) => String(v).trim()).filter(Boolean);
|
|
352
|
+
return values.length > 0 ? { type: "FILL_BLANK", values } : null;
|
|
353
|
+
}
|
|
354
|
+
};
|
|
355
|
+
var FILE_UPLOAD_CODEC = {
|
|
356
|
+
type: "FILE_UPLOAD",
|
|
357
|
+
toApiPayload(data, questionId) {
|
|
358
|
+
const obj = { fileKey: data.fileKey, fileName: data.fileName, mimeType: data.mimeType, size: data.size };
|
|
359
|
+
return { questionId, answer: JSON.stringify(obj) };
|
|
360
|
+
},
|
|
361
|
+
fromApiPayload(payload) {
|
|
362
|
+
if (!payload.answer)
|
|
363
|
+
return null;
|
|
364
|
+
const p = JSON.parse(payload.answer);
|
|
365
|
+
return {
|
|
366
|
+
type: "FILE_UPLOAD",
|
|
367
|
+
fileKey: p.fileKey,
|
|
368
|
+
fileName: p.fileName ?? "",
|
|
369
|
+
mimeType: p.mimeType,
|
|
370
|
+
size: p.size
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
};
|
|
374
|
+
var MATCHING_CODEC = {
|
|
375
|
+
type: "MATCHING",
|
|
376
|
+
toApiPayload(data, questionId) {
|
|
377
|
+
return { questionId, answer: JSON.stringify({ type: "MATCHING", value: data.pairs }) };
|
|
378
|
+
},
|
|
379
|
+
fromApiPayload(payload) {
|
|
380
|
+
if (!payload.answer)
|
|
381
|
+
return null;
|
|
382
|
+
const { value: pairs } = JSON.parse(payload.answer);
|
|
383
|
+
return pairs.length > 0 ? { type: "MATCHING", pairs } : null;
|
|
384
|
+
}
|
|
385
|
+
};
|
|
386
|
+
var ORDERING_CODEC = {
|
|
387
|
+
type: "ORDERING",
|
|
388
|
+
toApiPayload(data, questionId) {
|
|
389
|
+
return { questionId, answer: JSON.stringify({ type: "ORDERING", value: data.orderedValues }) };
|
|
390
|
+
},
|
|
391
|
+
fromApiPayload(payload) {
|
|
392
|
+
if (!payload.answer)
|
|
393
|
+
return null;
|
|
394
|
+
const { value: orderedValues } = JSON.parse(payload.answer);
|
|
395
|
+
return orderedValues.length > 0 ? { type: "ORDERING", orderedValues } : null;
|
|
396
|
+
}
|
|
397
|
+
};
|
|
398
|
+
var LINK_CODEC = {
|
|
399
|
+
type: "LINK",
|
|
400
|
+
toApiPayload(data, questionId) {
|
|
401
|
+
return { questionId, answer: JSON.stringify({ type: "LINK", links: data.urls }) };
|
|
402
|
+
},
|
|
403
|
+
fromApiPayload(payload) {
|
|
404
|
+
if (!payload.answer)
|
|
405
|
+
return null;
|
|
406
|
+
const { links } = JSON.parse(payload.answer);
|
|
407
|
+
const urls = links.map((v) => String(v).trim()).filter(Boolean);
|
|
408
|
+
return urls.length > 0 ? { type: "LINK", urls } : null;
|
|
409
|
+
}
|
|
410
|
+
};
|
|
411
|
+
var HOTSPOT_CODEC = {
|
|
412
|
+
type: "HOTSPOT",
|
|
413
|
+
toApiPayload(data, questionId) {
|
|
414
|
+
return { questionId, answer: JSON.stringify({ type: "HOTSPOT", value: data.coordinates }) };
|
|
415
|
+
},
|
|
416
|
+
fromApiPayload(payload) {
|
|
417
|
+
if (!payload.answer)
|
|
418
|
+
return null;
|
|
419
|
+
const { value: coordinates } = JSON.parse(payload.answer);
|
|
420
|
+
return coordinates.length > 0 ? { type: "HOTSPOT", coordinates } : null;
|
|
421
|
+
}
|
|
422
|
+
};
|
|
423
|
+
var ANSWER_CODECS = {
|
|
424
|
+
[QUESTION_TYPE_KEY.RADIO]: RADIO_CODEC,
|
|
425
|
+
[QUESTION_TYPE_KEY.CHECKBOX]: CHECKBOX_CODEC,
|
|
426
|
+
[QUESTION_TYPE_KEY.TRUE_FALSE]: TRUE_FALSE_CODEC,
|
|
427
|
+
[QUESTION_TYPE_KEY.TEXTAREA]: TEXTAREA_CODEC,
|
|
428
|
+
[QUESTION_TYPE_KEY.SHORT_ANSWER]: SHORT_ANSWER_CODEC,
|
|
429
|
+
[QUESTION_TYPE_KEY.NUMERIC]: NUMERIC_CODEC,
|
|
430
|
+
[QUESTION_TYPE_KEY.FILL_BLANK]: FILL_BLANK_CODEC,
|
|
431
|
+
[QUESTION_TYPE_KEY.FILE_UPLOAD]: FILE_UPLOAD_CODEC,
|
|
432
|
+
[QUESTION_TYPE_KEY.MATCHING]: MATCHING_CODEC,
|
|
433
|
+
[QUESTION_TYPE_KEY.ORDERING]: ORDERING_CODEC,
|
|
434
|
+
[QUESTION_TYPE_KEY.LINK]: LINK_CODEC,
|
|
435
|
+
[QUESTION_TYPE_KEY.HOTSPOT]: HOTSPOT_CODEC
|
|
436
|
+
};
|
|
437
|
+
|
|
438
|
+
// ../utils/dist/validation/constants.js
|
|
439
|
+
var ALLOWED_CONTENT_TYPES = ["video/mp4", "video/quicktime", "video/x-msvideo", "video/x-matroska"];
|
|
440
|
+
var ALLOWED_DOCUMENT_TYPES = [
|
|
441
|
+
"application/pdf",
|
|
442
|
+
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
443
|
+
// .docx
|
|
444
|
+
"application/msword"
|
|
445
|
+
// .doc
|
|
446
|
+
];
|
|
447
|
+
|
|
448
|
+
// ../utils/dist/validation/course/course.js
|
|
449
|
+
var ZCourseClone = z2.object({
|
|
450
|
+
title: z2.string().min(1),
|
|
451
|
+
description: z2.string().optional(),
|
|
452
|
+
slug: z2.string().min(1),
|
|
453
|
+
organizationId: z2.string().min(1)
|
|
454
|
+
});
|
|
455
|
+
var ZCourseCloneParam = z2.object({
|
|
456
|
+
courseId: z2.string().min(1)
|
|
457
|
+
});
|
|
458
|
+
var ZCourseGetParam = z2.object({
|
|
459
|
+
courseId: z2.string().min(1)
|
|
460
|
+
});
|
|
461
|
+
var ZCourseGetQuery = z2.object({
|
|
462
|
+
slug: z2.string().optional()
|
|
463
|
+
});
|
|
464
|
+
var ZCourseGetBySlugParam = z2.object({
|
|
465
|
+
slug: z2.string().min(1)
|
|
466
|
+
});
|
|
467
|
+
var ZCourseEnrollParam = z2.object({
|
|
468
|
+
courseId: z2.string().min(1)
|
|
469
|
+
});
|
|
470
|
+
var ZCourseEnrollBody = z2.object({
|
|
471
|
+
inviteToken: z2.string().min(1).optional()
|
|
472
|
+
});
|
|
473
|
+
var ZCourseDownloadParam = z2.object({
|
|
474
|
+
courseId: z2.string().min(1)
|
|
475
|
+
});
|
|
476
|
+
var ZCertificateDownload = z2.object({
|
|
477
|
+
theme: z2.string().min(1),
|
|
478
|
+
studentName: z2.string().min(1),
|
|
479
|
+
courseName: z2.string().min(1),
|
|
480
|
+
courseDescription: z2.string().min(1),
|
|
481
|
+
orgName: z2.string().min(1),
|
|
482
|
+
orgLogoUrl: z2.string().url().optional(),
|
|
483
|
+
facilitator: z2.string().optional()
|
|
484
|
+
});
|
|
485
|
+
var ZCourseDownloadContent = z2.object({
|
|
486
|
+
courseTitle: z2.string().min(1),
|
|
487
|
+
orgName: z2.string().min(1),
|
|
488
|
+
orgTheme: z2.string().min(1),
|
|
489
|
+
lessons: z2.array(z2.object({
|
|
490
|
+
lessonTitle: z2.string().min(1),
|
|
491
|
+
lessonNumber: z2.string().min(1),
|
|
492
|
+
lessonNote: z2.string(),
|
|
493
|
+
slideUrl: z2.string().url().optional(),
|
|
494
|
+
video: z2.array(z2.string().url()).optional()
|
|
495
|
+
}))
|
|
496
|
+
});
|
|
497
|
+
var ZLessonDownloadContent = z2.object({
|
|
498
|
+
title: z2.string().min(1),
|
|
499
|
+
number: z2.string().min(1),
|
|
500
|
+
orgName: z2.string().min(1),
|
|
501
|
+
note: z2.string(),
|
|
502
|
+
slideUrl: z2.string().url().optional(),
|
|
503
|
+
video: z2.array(z2.string().url()).optional(),
|
|
504
|
+
courseTitle: z2.string().min(1)
|
|
505
|
+
});
|
|
506
|
+
var ZCoursePresignUrlUpload = z2.object({
|
|
507
|
+
fileName: z2.string().min(1),
|
|
508
|
+
fileType: z2.enum(ALLOWED_CONTENT_TYPES)
|
|
509
|
+
});
|
|
510
|
+
var ZCourseDocumentPresignUrlUpload = z2.object({
|
|
511
|
+
fileName: z2.string().min(1),
|
|
512
|
+
fileType: z2.enum(ALLOWED_DOCUMENT_TYPES)
|
|
513
|
+
});
|
|
514
|
+
var ZCourseDownloadPresignedUrl = z2.object({
|
|
515
|
+
keys: z2.array(z2.string().min(1)).min(1)
|
|
516
|
+
});
|
|
517
|
+
var ZCourseContentUpdateItem = z2.object({
|
|
518
|
+
id: z2.string().min(1),
|
|
519
|
+
type: z2.enum(["LESSON", "EXERCISE"]),
|
|
520
|
+
isUnlocked: z2.boolean().optional(),
|
|
521
|
+
order: z2.number().int().min(0).optional(),
|
|
522
|
+
sectionId: z2.string().nullable().optional()
|
|
523
|
+
});
|
|
524
|
+
var ZCourseContentUpdate = z2.object({
|
|
525
|
+
items: z2.array(ZCourseContentUpdateItem).min(1)
|
|
526
|
+
});
|
|
527
|
+
var ZCourseContentDeleteItem = z2.object({
|
|
528
|
+
id: z2.string().min(1),
|
|
529
|
+
type: z2.enum(["LESSON", "EXERCISE"])
|
|
530
|
+
});
|
|
531
|
+
var ZCourseContentDelete = z2.object({
|
|
532
|
+
sectionId: z2.string().min(1).optional(),
|
|
533
|
+
items: z2.array(ZCourseContentDeleteItem).min(1).optional()
|
|
534
|
+
}).refine((data) => Boolean(data.sectionId) !== Boolean(data.items), {
|
|
535
|
+
message: "Provide either sectionId or items",
|
|
536
|
+
path: ["sectionId"]
|
|
537
|
+
});
|
|
538
|
+
var ZCourseCreate = z2.object({
|
|
539
|
+
title: z2.string().min(1),
|
|
540
|
+
description: z2.string().min(1),
|
|
541
|
+
type: z2.enum(["LIVE_CLASS", "SELF_PACED"]),
|
|
542
|
+
organizationId: z2.string().min(1)
|
|
543
|
+
});
|
|
544
|
+
var ZCourseMetadata = z2.object({
|
|
545
|
+
requirements: z2.string().optional(),
|
|
546
|
+
description: z2.string().optional(),
|
|
547
|
+
goals: z2.string().optional(),
|
|
548
|
+
videoUrl: z2.string().optional(),
|
|
549
|
+
showDiscount: z2.boolean().optional(),
|
|
550
|
+
discount: z2.number().optional(),
|
|
551
|
+
paymentLink: z2.string().optional(),
|
|
552
|
+
reward: z2.object({
|
|
553
|
+
show: z2.boolean(),
|
|
554
|
+
description: z2.string()
|
|
555
|
+
}).optional(),
|
|
556
|
+
instructor: z2.object({
|
|
557
|
+
name: z2.string(),
|
|
558
|
+
role: z2.string(),
|
|
559
|
+
coursesNo: z2.number(),
|
|
560
|
+
description: z2.string(),
|
|
561
|
+
imgUrl: z2.string()
|
|
562
|
+
}).optional(),
|
|
563
|
+
certificate: z2.object({
|
|
564
|
+
templateUrl: z2.string()
|
|
565
|
+
}).optional(),
|
|
566
|
+
reviews: z2.array(z2.object({
|
|
567
|
+
id: z2.number(),
|
|
568
|
+
hide: z2.boolean(),
|
|
569
|
+
name: z2.string(),
|
|
570
|
+
avatar_url: z2.string(),
|
|
571
|
+
rating: z2.number(),
|
|
572
|
+
created_at: z2.number(),
|
|
573
|
+
description: z2.string()
|
|
574
|
+
})).optional(),
|
|
575
|
+
lessonTabsOrder: z2.array(z2.object({
|
|
576
|
+
id: z2.union([z2.literal(1), z2.literal(2), z2.literal(3), z2.literal(4)]),
|
|
577
|
+
name: z2.string()
|
|
578
|
+
})).optional(),
|
|
579
|
+
grading: z2.boolean().optional(),
|
|
580
|
+
lessonDownload: z2.boolean().optional(),
|
|
581
|
+
allowNewStudent: z2.boolean(),
|
|
582
|
+
sectionDisplay: z2.record(z2.string(), z2.boolean()).optional(),
|
|
583
|
+
isContentGroupingEnabled: z2.boolean().optional()
|
|
584
|
+
});
|
|
585
|
+
var ZCourseUpdate = z2.object({
|
|
586
|
+
title: z2.string().min(1).optional(),
|
|
587
|
+
description: z2.string().min(1).optional(),
|
|
588
|
+
type: z2.enum(["LIVE_CLASS", "SELF_PACED"]).optional(),
|
|
589
|
+
logo: z2.string().optional(),
|
|
590
|
+
slug: z2.string().optional(),
|
|
591
|
+
isPublished: z2.boolean().optional(),
|
|
592
|
+
overview: z2.string().optional(),
|
|
593
|
+
metadata: ZCourseMetadata.optional(),
|
|
594
|
+
isCertificateDownloadable: z2.boolean().optional(),
|
|
595
|
+
certificateTheme: z2.string().optional(),
|
|
596
|
+
tagIds: z2.array(z2.uuid()).max(100).optional()
|
|
597
|
+
});
|
|
598
|
+
var ZCourseUpdateParam = z2.object({
|
|
599
|
+
courseId: z2.string().min(1)
|
|
600
|
+
});
|
|
601
|
+
var ZCourseDeleteParam = z2.object({
|
|
602
|
+
courseId: z2.string().min(1)
|
|
603
|
+
});
|
|
604
|
+
var ZCourseProgressParam = z2.object({
|
|
605
|
+
courseId: z2.string().min(1)
|
|
606
|
+
});
|
|
607
|
+
var ZCourseProgressQuery = z2.object({
|
|
608
|
+
profileId: z2.string().uuid()
|
|
609
|
+
});
|
|
610
|
+
var ZCourseUserAnalyticsParam = z2.object({
|
|
611
|
+
courseId: z2.string().min(1),
|
|
612
|
+
userId: z2.string().uuid()
|
|
613
|
+
});
|
|
614
|
+
|
|
615
|
+
// ../utils/dist/validation/course-import/course-import.js
|
|
616
|
+
var ZSupportedLocale = z3.enum(["en", "hi", "fr", "pt", "de", "vi", "ru", "es", "pl", "da"]);
|
|
617
|
+
var ZCourseImportWarning = z3.object({
|
|
618
|
+
code: z3.string().min(1),
|
|
619
|
+
message: z3.string().min(1),
|
|
620
|
+
severity: z3.enum(["info", "warning", "error"])
|
|
621
|
+
});
|
|
622
|
+
var ZCourseImportSourceReference = z3.object({
|
|
623
|
+
type: z3.enum(["prompt", "pdf", "course"]),
|
|
624
|
+
label: z3.string().min(1),
|
|
625
|
+
pageStart: z3.number().int().min(1).optional(),
|
|
626
|
+
pageEnd: z3.number().int().min(1).optional()
|
|
627
|
+
});
|
|
628
|
+
var ZCourseImportDraftCourse = z3.object({
|
|
629
|
+
title: z3.string().min(1),
|
|
630
|
+
description: z3.string().min(1),
|
|
631
|
+
type: z3.enum(["LIVE_CLASS", "SELF_PACED"]),
|
|
632
|
+
locale: ZSupportedLocale.default("en"),
|
|
633
|
+
metadata: ZCourseMetadata.optional()
|
|
634
|
+
});
|
|
635
|
+
var ZCourseImportDraftSection = z3.object({
|
|
636
|
+
externalId: z3.string().min(1),
|
|
637
|
+
title: z3.string().min(1),
|
|
638
|
+
order: z3.number().int().min(0)
|
|
639
|
+
});
|
|
640
|
+
var ZCourseImportDraftLesson = z3.object({
|
|
641
|
+
externalId: z3.string().min(1),
|
|
642
|
+
sectionExternalId: z3.string().min(1),
|
|
643
|
+
title: z3.string().min(1),
|
|
644
|
+
order: z3.number().int().min(0),
|
|
645
|
+
isUnlocked: z3.boolean().optional(),
|
|
646
|
+
public: z3.boolean().optional()
|
|
647
|
+
});
|
|
648
|
+
var ZCourseImportDraftLessonLanguage = z3.object({
|
|
649
|
+
lessonExternalId: z3.string().min(1),
|
|
650
|
+
locale: ZSupportedLocale,
|
|
651
|
+
content: z3.string().min(1)
|
|
652
|
+
});
|
|
653
|
+
var ZCourseImportDraftPayload = z3.object({
|
|
654
|
+
course: ZCourseImportDraftCourse,
|
|
655
|
+
sections: z3.array(ZCourseImportDraftSection).min(1),
|
|
656
|
+
lessons: z3.array(ZCourseImportDraftLesson).min(1),
|
|
657
|
+
lessonLanguages: z3.array(ZCourseImportDraftLessonLanguage).min(1),
|
|
658
|
+
exercises: z3.array(z3.record(z3.string(), z3.unknown())).optional(),
|
|
659
|
+
sourceReferences: z3.array(ZCourseImportSourceReference).optional(),
|
|
660
|
+
warnings: z3.array(ZCourseImportWarning).default([])
|
|
661
|
+
}).superRefine((value, ctx) => {
|
|
662
|
+
const sectionIds = /* @__PURE__ */ new Set();
|
|
663
|
+
value.sections.forEach((section, index) => {
|
|
664
|
+
if (sectionIds.has(section.externalId)) {
|
|
665
|
+
ctx.addIssue({
|
|
666
|
+
code: z3.ZodIssueCode.custom,
|
|
667
|
+
path: ["sections", index, "externalId"],
|
|
668
|
+
message: "Section externalId must be unique"
|
|
669
|
+
});
|
|
670
|
+
}
|
|
671
|
+
sectionIds.add(section.externalId);
|
|
672
|
+
});
|
|
673
|
+
const lessonIds = /* @__PURE__ */ new Set();
|
|
674
|
+
value.lessons.forEach((lesson, index) => {
|
|
675
|
+
if (lessonIds.has(lesson.externalId)) {
|
|
676
|
+
ctx.addIssue({
|
|
677
|
+
code: z3.ZodIssueCode.custom,
|
|
678
|
+
path: ["lessons", index, "externalId"],
|
|
679
|
+
message: "Lesson externalId must be unique"
|
|
680
|
+
});
|
|
681
|
+
}
|
|
682
|
+
lessonIds.add(lesson.externalId);
|
|
683
|
+
if (!sectionIds.has(lesson.sectionExternalId)) {
|
|
684
|
+
ctx.addIssue({
|
|
685
|
+
code: z3.ZodIssueCode.custom,
|
|
686
|
+
path: ["lessons", index, "sectionExternalId"],
|
|
687
|
+
message: "Lesson sectionExternalId must reference an existing section"
|
|
688
|
+
});
|
|
689
|
+
}
|
|
690
|
+
});
|
|
691
|
+
value.lessonLanguages.forEach((lessonLanguage, index) => {
|
|
692
|
+
if (!lessonIds.has(lessonLanguage.lessonExternalId)) {
|
|
693
|
+
ctx.addIssue({
|
|
694
|
+
code: z3.ZodIssueCode.custom,
|
|
695
|
+
path: ["lessonLanguages", index, "lessonExternalId"],
|
|
696
|
+
message: "Lesson language must reference an existing lesson"
|
|
697
|
+
});
|
|
698
|
+
}
|
|
699
|
+
});
|
|
700
|
+
});
|
|
701
|
+
var ZCourseImportDraftCreate = z3.object({
|
|
702
|
+
sourceType: z3.enum(["prompt", "pdf", "course"]),
|
|
703
|
+
idempotencyKey: z3.string().min(1).optional(),
|
|
704
|
+
summary: z3.record(z3.string(), z3.unknown()).optional(),
|
|
705
|
+
sourceArtifacts: z3.array(z3.record(z3.string(), z3.unknown())).optional(),
|
|
706
|
+
draft: ZCourseImportDraftPayload
|
|
707
|
+
});
|
|
708
|
+
var ZCourseImportCourseParam = z3.object({
|
|
709
|
+
courseId: z3.string().min(1)
|
|
710
|
+
});
|
|
711
|
+
var ZCourseImportDraftCreateFromCourse = z3.object({
|
|
712
|
+
courseId: z3.string().min(1),
|
|
713
|
+
idempotencyKey: z3.string().min(1).optional(),
|
|
714
|
+
summary: z3.record(z3.string(), z3.unknown()).optional(),
|
|
715
|
+
sourceArtifacts: z3.array(z3.record(z3.string(), z3.unknown())).optional()
|
|
716
|
+
});
|
|
717
|
+
var ZCourseImportDraftGetParam = z3.object({
|
|
718
|
+
draftId: z3.string().uuid()
|
|
719
|
+
});
|
|
720
|
+
var ZCourseImportDraftUpdate = z3.object({
|
|
721
|
+
summary: z3.record(z3.string(), z3.unknown()).optional(),
|
|
722
|
+
sourceArtifacts: z3.array(z3.record(z3.string(), z3.unknown())).optional(),
|
|
723
|
+
warnings: z3.array(ZCourseImportWarning).optional(),
|
|
724
|
+
draft: ZCourseImportDraftPayload.optional()
|
|
725
|
+
});
|
|
726
|
+
var ZCourseImportDraftPublish = z3.object({
|
|
727
|
+
title: z3.string().min(1).optional(),
|
|
728
|
+
description: z3.string().min(1).optional(),
|
|
729
|
+
type: z3.enum(["LIVE_CLASS", "SELF_PACED"]).optional(),
|
|
730
|
+
metadata: ZCourseMetadata.optional()
|
|
731
|
+
});
|
|
732
|
+
var ZCourseImportDraftPublishToCourse = ZCourseImportDraftPublish.extend({
|
|
733
|
+
courseId: z3.string().min(1)
|
|
734
|
+
});
|
|
735
|
+
|
|
736
|
+
// src/tools/course-drafts.ts
|
|
737
|
+
var ZUpdateCourseDraftToolInput = ZCourseImportDraftUpdate.extend({
|
|
738
|
+
draftId: ZCourseImportDraftGetParam.shape.draftId
|
|
739
|
+
});
|
|
740
|
+
var ZPublishCourseDraftToolInput = ZCourseImportDraftPublish.extend({
|
|
741
|
+
draftId: ZCourseImportDraftGetParam.shape.draftId
|
|
742
|
+
});
|
|
743
|
+
var ZPublishCourseDraftToExistingCourseToolInput = ZCourseImportDraftPublishToCourse.extend({
|
|
744
|
+
draftId: ZCourseImportDraftGetParam.shape.draftId
|
|
745
|
+
});
|
|
746
|
+
var createCourseDraftShape = ZCourseImportDraftCreate.shape;
|
|
747
|
+
var createCourseDraftFromCourseShape = ZCourseImportDraftCreateFromCourse.shape;
|
|
748
|
+
var getCourseStructureShape = ZCourseImportCourseParam.shape;
|
|
749
|
+
var getCourseDraftShape = ZCourseImportDraftGetParam.shape;
|
|
750
|
+
var updateCourseDraftShape = ZUpdateCourseDraftToolInput.shape;
|
|
751
|
+
var publishCourseDraftShape = ZPublishCourseDraftToolInput.shape;
|
|
752
|
+
var publishCourseDraftToExistingCourseShape = ZPublishCourseDraftToExistingCourseToolInput.shape;
|
|
753
|
+
function registerCourseDraftTools(server, apiClient) {
|
|
754
|
+
server.tool("get_course_structure", getCourseStructureShape, async (args) => {
|
|
755
|
+
const { courseId } = ZCourseImportCourseParam.parse(args);
|
|
756
|
+
const result = await apiClient.getCourseStructure(courseId);
|
|
757
|
+
return jsonContent(result);
|
|
758
|
+
});
|
|
759
|
+
server.tool("create_course_draft", createCourseDraftShape, async (args) => {
|
|
760
|
+
const payload = ZCourseImportDraftCreate.parse(args);
|
|
761
|
+
const result = await apiClient.createCourseDraft(payload);
|
|
762
|
+
return jsonContent(result);
|
|
763
|
+
});
|
|
764
|
+
server.tool("create_course_draft_from_course", createCourseDraftFromCourseShape, async (args) => {
|
|
765
|
+
const payload = ZCourseImportDraftCreateFromCourse.parse(args);
|
|
766
|
+
const result = await apiClient.createCourseDraftFromCourse(payload);
|
|
767
|
+
return jsonContent(result);
|
|
768
|
+
});
|
|
769
|
+
server.tool("get_course_draft", getCourseDraftShape, async (args) => {
|
|
770
|
+
const { draftId } = ZCourseImportDraftGetParam.parse(args);
|
|
771
|
+
const result = await apiClient.getCourseDraft(draftId);
|
|
772
|
+
return jsonContent(result);
|
|
773
|
+
});
|
|
774
|
+
server.tool("update_course_draft", updateCourseDraftShape, async (args) => {
|
|
775
|
+
const { draftId, ...payload } = ZUpdateCourseDraftToolInput.parse(args);
|
|
776
|
+
const result = await apiClient.updateCourseDraft(draftId, payload);
|
|
777
|
+
return jsonContent(result);
|
|
778
|
+
});
|
|
779
|
+
server.tool("publish_course_draft", publishCourseDraftShape, async (args) => {
|
|
780
|
+
const { draftId, ...payload } = ZPublishCourseDraftToolInput.parse(args);
|
|
781
|
+
const result = await apiClient.publishCourseDraft(draftId, payload);
|
|
782
|
+
return jsonContent(result);
|
|
783
|
+
});
|
|
784
|
+
server.tool("publish_course_draft_to_existing_course", publishCourseDraftToExistingCourseShape, async (args) => {
|
|
785
|
+
const { draftId, ...payload } = ZPublishCourseDraftToExistingCourseToolInput.parse(args);
|
|
786
|
+
const result = await apiClient.publishCourseDraftToExistingCourse(draftId, payload);
|
|
787
|
+
return jsonContent(result);
|
|
788
|
+
});
|
|
789
|
+
}
|
|
790
|
+
function jsonContent(data) {
|
|
791
|
+
return {
|
|
792
|
+
content: [
|
|
793
|
+
{
|
|
794
|
+
type: "text",
|
|
795
|
+
text: JSON.stringify(data)
|
|
796
|
+
}
|
|
797
|
+
]
|
|
798
|
+
};
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
// src/index.ts
|
|
802
|
+
async function main() {
|
|
803
|
+
const config = getConfig();
|
|
804
|
+
const apiClient = new ClassroomIoApiClient(config);
|
|
805
|
+
const server = new McpServer({
|
|
806
|
+
name: "classroomio-course-authoring",
|
|
807
|
+
version: "0.0.1"
|
|
808
|
+
});
|
|
809
|
+
registerCourseDraftTools(server, apiClient);
|
|
810
|
+
const transport = new StdioServerTransport();
|
|
811
|
+
await server.connect(transport);
|
|
812
|
+
process.stdin.on("close", () => {
|
|
813
|
+
void server.close();
|
|
814
|
+
});
|
|
815
|
+
}
|
|
816
|
+
main().catch((error) => {
|
|
817
|
+
if (error instanceof ClassroomIoApiError) {
|
|
818
|
+
console.error(
|
|
819
|
+
JSON.stringify({
|
|
820
|
+
error: error.message,
|
|
821
|
+
status: error.status,
|
|
822
|
+
code: error.code,
|
|
823
|
+
field: error.field
|
|
824
|
+
})
|
|
825
|
+
);
|
|
826
|
+
process.exit(1);
|
|
827
|
+
}
|
|
828
|
+
console.error(error);
|
|
829
|
+
process.exit(1);
|
|
830
|
+
});
|