@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.
Files changed (4) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +273 -0
  3. package/dist/index.js +830 -0
  4. 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
+ });