@classroomio/mcp 0.0.2 → 0.0.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.
Files changed (3) hide show
  1. package/README.md +97 -1
  2. package/dist/index.js +655 -251
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -45,7 +45,14 @@ Current tools:
45
45
  - `create_course_draft`
46
46
  - `create_course_draft_from_course`
47
47
  - `get_course_draft`
48
+ - `list_course_exercises`
49
+ - `get_course_exercise`
50
+ - `create_course_exercise`
51
+ - `create_course_exercise_from_template`
52
+ - `update_course_exercise`
48
53
  - `update_course_draft`
54
+ - `tag_course_draft`
55
+ - `tag_courses`
49
56
  - `publish_course_draft`
50
57
  - `publish_course_draft_to_existing_course`
51
58
 
@@ -139,6 +146,7 @@ Draft payload shape:
139
146
  ```text
140
147
  draft
141
148
  course
149
+ tags[]
142
150
  sections[]
143
151
  lessons[]
144
152
  lessonLanguages[]
@@ -154,6 +162,31 @@ Drafts support:
154
162
  - safer updates to existing courses
155
163
  - auditability of AI-generated structure
156
164
 
165
+ ## After Publish
166
+
167
+ Once a draft has been published, the live course becomes the safer source of truth.
168
+
169
+ Recommended agent rule:
170
+
171
+ - if the draft is still `DRAFT`, continue editing that draft
172
+ - if the draft is `PUBLISHED`, do not keep reusing it for major follow-up edits
173
+ - instead, read the live course and create a fresh draft from that course
174
+
175
+ Recommended post-publish sequence:
176
+
177
+ 1. `get_course_structure`
178
+ 2. `create_course_draft_from_course`
179
+ 3. `update_course_draft`
180
+ 4. `publish_course_draft_to_existing_course`
181
+
182
+ Why:
183
+
184
+ - teachers may edit the live course in the UI after publish
185
+ - another agent session may have changed the course
186
+ - reusing an old published draft can overwrite newer live changes
187
+
188
+ For exercises on an already-published course, prefer the direct exercise tools instead of creating a whole new draft when the user only wants to add or revise exercises.
189
+
157
190
  ## User Flows
158
191
 
159
192
  ### Flow 1: Create a new course from a prompt
@@ -178,6 +211,8 @@ Result:
178
211
  - a new course is created
179
212
  - sections and lessons are created
180
213
  - localized lesson content is written
214
+ - publish returns the live `courseId` and public `courseUrl`
215
+ - publish can optionally set a random Unsplash-derived course cover
181
216
 
182
217
  ### Flow 2: Create a draft from a PDF
183
218
 
@@ -202,7 +237,27 @@ Important:
202
237
  - the PDF does not need to be uploaded to MCP
203
238
  - the MCP package only receives structured JSON
204
239
 
205
- ### Flow 3: Update an existing course safely
240
+ ### Flow 3: Tag a draft before publish
241
+
242
+ User says:
243
+
244
+ ```text
245
+ Add tags for algebra, beginner, and exponential growth to this draft.
246
+ ```
247
+
248
+ Expected tool sequence:
249
+
250
+ 1. Agent calls `tag_course_draft`.
251
+ 2. User reviews the updated draft tags.
252
+ 3. Agent later calls `publish_course_draft`.
253
+
254
+ Result:
255
+
256
+ - the draft stores tag names
257
+ - publish ensures the tags exist in ClassroomIO
258
+ - publish assigns those tags to the live course
259
+
260
+ ### Flow 4: Update an existing course safely
206
261
 
207
262
  User says:
208
263
 
@@ -221,10 +276,29 @@ Expected tool sequence:
221
276
  What this publish does today:
222
277
 
223
278
  - updates the existing course title, description, type, and metadata
279
+ - can set a generated course cover from Unsplash
280
+ - returns the public `courseUrl`
224
281
  - updates existing sections when the draft keeps their IDs
225
282
  - updates existing lessons when the draft keeps their IDs
226
283
  - creates new sections and lessons for draft items without matching live IDs
227
284
  - upserts lesson language content
285
+ - merges draft tags into the existing course
286
+
287
+ ### Flow 5: Add exercises to a live course
288
+
289
+ User says:
290
+
291
+ ```text
292
+ Add a 5-question multiple-choice exercise to lesson 3.
293
+ ```
294
+
295
+ Expected tool sequence:
296
+
297
+ 1. Agent calls `list_course_exercises` if it needs current context.
298
+ 2. Agent calls `create_course_exercise` for a brand new exercise.
299
+ 3. Agent calls `update_course_exercise` for later revisions.
300
+
301
+ Use this direct path when the course is already live and the change is exercise-specific.
228
302
 
229
303
  What it does not do today:
230
304
 
@@ -253,6 +327,28 @@ For large updates, the agent should:
253
327
  3. only generate new `externalId` values for new records
254
328
  4. avoid deleting records from the draft unless the user explicitly wants omitted content to remain untouched
255
329
 
330
+ Publish input also supports:
331
+
332
+ - `bannerImageUrl`: use this exact course cover
333
+ - `bannerImageQuery`: search Unsplash with this query
334
+ - `generateBannerImage`: fetch a random Unsplash image using the course title or query
335
+
336
+ Tagging tools use:
337
+
338
+ - `tagNames`: human-readable tag names, not tag IDs
339
+ - `mode: "merge" | "replace"`
340
+ - `groupName` optional, defaults to `Automation`
341
+
342
+ The API creates missing tags automatically when needed.
343
+
344
+ Exercise tools use the live course directly:
345
+
346
+ - `list_course_exercises`
347
+ - `get_course_exercise`
348
+ - `create_course_exercise`
349
+ - `create_course_exercise_from_template`
350
+ - `update_course_exercise`
351
+
256
352
  ## Operational Notes
257
353
 
258
354
  - `create_course_draft_from_course` may add warnings if a live course uses legacy lesson notes instead of localized lesson content
package/dist/index.js CHANGED
@@ -40,12 +40,53 @@ var ClassroomIoApiClient = class {
40
40
  method: "GET"
41
41
  });
42
42
  }
43
+ async listCourseExercises(courseId, query = {}) {
44
+ const searchParams = new URLSearchParams();
45
+ if (query.lessonId) searchParams.set("lessonId", query.lessonId);
46
+ if (query.sectionId) searchParams.set("sectionId", query.sectionId);
47
+ const querySuffix = searchParams.toString() ? `?${searchParams.toString()}` : "";
48
+ return this.request(`/course/${courseId}/exercise${querySuffix}`, {
49
+ method: "GET"
50
+ });
51
+ }
52
+ async getCourseExercise(courseId, exerciseId) {
53
+ return this.request(`/course/${courseId}/exercise/${exerciseId}`, {
54
+ method: "GET"
55
+ });
56
+ }
57
+ async createCourseExercise(courseId, payload) {
58
+ return this.request(`/course/${courseId}/exercise`, {
59
+ method: "POST",
60
+ body: {
61
+ ...payload,
62
+ courseId
63
+ }
64
+ });
65
+ }
66
+ async createCourseExerciseFromTemplate(courseId, payload) {
67
+ return this.request(`/course/${courseId}/exercise/from-template`, {
68
+ method: "POST",
69
+ body: payload
70
+ });
71
+ }
72
+ async updateCourseExercise(courseId, exerciseId, payload) {
73
+ return this.request(`/course/${courseId}/exercise/${exerciseId}`, {
74
+ method: "PUT",
75
+ body: payload
76
+ });
77
+ }
43
78
  async updateCourseDraft(draftId, payload) {
44
79
  return this.request(`/organization/course-import/drafts/${draftId}`, {
45
80
  method: "PUT",
46
81
  body: payload
47
82
  });
48
83
  }
84
+ async tagCourseDraft(draftId, payload) {
85
+ return this.request(`/organization/course-import/drafts/${draftId}/tags`, {
86
+ method: "PUT",
87
+ body: payload
88
+ });
89
+ }
49
90
  async publishCourseDraft(draftId, payload) {
50
91
  return this.request(`/organization/course-import/drafts/${draftId}/publish`, {
51
92
  method: "POST",
@@ -58,6 +99,15 @@ var ClassroomIoApiClient = class {
58
99
  body: payload
59
100
  });
60
101
  }
102
+ async tagCourses(payload) {
103
+ return this.request(
104
+ "/organization/tags/courses/assign",
105
+ {
106
+ method: "PUT",
107
+ body: payload
108
+ }
109
+ );
110
+ }
61
111
  async request(path, options) {
62
112
  const response = await fetch(new URL(path, this.config.CLASSROOMIO_API_URL), {
63
113
  method: options.method,
@@ -96,11 +146,71 @@ function getConfig(env = process.env) {
96
146
  return ZConfig.parse(env);
97
147
  }
98
148
 
99
- // ../utils/dist/validation/course-import/course-import.js
100
- import * as z3 from "zod";
101
-
102
- // ../utils/dist/validation/course/course.js
149
+ // ../utils/dist/validation/tag/tag.js
103
150
  import * as z2 from "zod";
151
+ var TAG_COLOR_OPTIONS = [
152
+ "#EF4444",
153
+ "#F97316",
154
+ "#F59E0B",
155
+ "#84CC16",
156
+ "#22C55E",
157
+ "#14B8A6",
158
+ "#06B6D4",
159
+ "#3B82F6",
160
+ "#8B5CF6",
161
+ "#EC4899"
162
+ ];
163
+ var ZTagColor = z2.enum(TAG_COLOR_OPTIONS);
164
+ var ZTagGroupParam = z2.object({
165
+ groupId: z2.uuid()
166
+ });
167
+ var ZTagGroupCreate = z2.object({
168
+ name: z2.string().min(1).max(80),
169
+ description: z2.string().max(240).optional(),
170
+ order: z2.number().int().min(0).optional()
171
+ });
172
+ var ZTagGroupUpdate = ZTagGroupCreate.partial().refine((value) => Object.keys(value).length > 0, {
173
+ message: "At least one field is required"
174
+ });
175
+ var ZTagParam = z2.object({
176
+ tagId: z2.uuid()
177
+ });
178
+ var ZTagCreate = z2.object({
179
+ name: z2.string().min(1).max(80),
180
+ description: z2.string().max(240).optional(),
181
+ groupId: z2.uuid(),
182
+ color: ZTagColor
183
+ });
184
+ var ZTagUpdate = z2.object({
185
+ name: z2.string().min(1).max(80).optional(),
186
+ description: z2.string().max(240).optional(),
187
+ groupId: z2.uuid().optional(),
188
+ color: ZTagColor.optional()
189
+ }).refine((value) => Object.keys(value).length > 0, {
190
+ message: "At least one field is required"
191
+ });
192
+ var ZCourseTagParam = z2.object({
193
+ courseId: z2.uuid()
194
+ });
195
+ var ZCourseTagAssignment = z2.object({
196
+ tagIds: z2.array(z2.uuid()).max(100).default([])
197
+ });
198
+ var ZAutomationTagMode = z2.enum(["merge", "replace"]).default("merge");
199
+ var ZAutomationTagName = z2.string().trim().min(1).max(80);
200
+ var ZAutomationDraftTagParam = z2.object({
201
+ draftId: z2.string().uuid()
202
+ });
203
+ var ZAutomationDraftTagAssignment = z2.object({
204
+ tagNames: z2.array(ZAutomationTagName).min(1).max(100),
205
+ mode: ZAutomationTagMode,
206
+ groupName: z2.string().trim().min(1).max(80).optional()
207
+ });
208
+ var ZAutomationCourseTagAssignment = ZAutomationDraftTagAssignment.extend({
209
+ courseIds: z2.array(z2.string().uuid()).min(1).max(100)
210
+ });
211
+
212
+ // ../utils/dist/validation/exercise/exercise.js
213
+ import * as z3 from "zod";
104
214
 
105
215
  // ../question-types/dist/question-type-keys.js
106
216
  var QUESTION_TYPE_KEY = {
@@ -444,226 +554,384 @@ var ALLOWED_DOCUMENT_TYPES = [
444
554
  "application/msword"
445
555
  // .doc
446
556
  ];
557
+ var QUESTION_TYPE = QUESTION_TYPE_IDS;
558
+
559
+ // ../utils/dist/validation/exercise/exercise.js
560
+ var QUESTION_VALIDATION_RULES = {
561
+ [QUESTION_TYPE.RADIO]: [
562
+ (question) => {
563
+ const options = question.options || [];
564
+ if (options.length > 0 && options.length < 2) {
565
+ return "Options must have at least 2 items for RADIO/CHECKBOX questions";
566
+ }
567
+ return null;
568
+ },
569
+ (question) => {
570
+ const options = question.options || [];
571
+ if (options.length > 0 && !options.some((opt) => opt.isCorrect === true)) {
572
+ return "Please mark an option as the correct answer";
573
+ }
574
+ return null;
575
+ }
576
+ ],
577
+ [QUESTION_TYPE.CHECKBOX]: [
578
+ (question) => {
579
+ const options = question.options || [];
580
+ if (options.length > 0 && options.length < 2) {
581
+ return "Options must have at least 2 items for RADIO/CHECKBOX questions";
582
+ }
583
+ return null;
584
+ },
585
+ (question) => {
586
+ const options = question.options || [];
587
+ if (options.length > 0 && !options.some((opt) => opt.isCorrect === true)) {
588
+ return "Please mark an option as the correct answer";
589
+ }
590
+ return null;
591
+ }
592
+ ],
593
+ [QUESTION_TYPE.TRUE_FALSE]: [
594
+ (question) => {
595
+ const options = question.options || [];
596
+ if (options.length > 0 && options.length < 2) {
597
+ return "True/False questions need both True and False options";
598
+ }
599
+ return null;
600
+ },
601
+ (question) => {
602
+ const options = question.options || [];
603
+ if (options.length > 0 && !options.some((opt) => opt.isCorrect === true)) {
604
+ return "Please mark an option as the correct answer";
605
+ }
606
+ return null;
607
+ }
608
+ ],
609
+ [QUESTION_TYPE.TEXTAREA]: []
610
+ };
611
+ var ZExerciseUpdateQuestionBase = z3.object({
612
+ id: z3.number().int().optional(),
613
+ question: z3.string().min(1),
614
+ questionTypeId: z3.number().int().min(1).optional(),
615
+ // Added to support question type updates
616
+ points: z3.number().int().min(0).optional(),
617
+ order: z3.number().int().min(0).optional(),
618
+ settings: z3.record(z3.string(), z3.unknown()).optional(),
619
+ deletedAt: z3.string().optional(),
620
+ // Marks question as deleted
621
+ options: z3.array(z3.object({
622
+ id: z3.number().int().optional(),
623
+ label: z3.string().min(1),
624
+ isCorrect: z3.boolean(),
625
+ settings: z3.record(z3.string(), z3.unknown()).optional(),
626
+ deletedAt: z3.string().optional()
627
+ // Marks option as deleted
628
+ })).optional()
629
+ });
630
+ function validateQuestionOptions(question, ctx) {
631
+ if (question.deletedAt)
632
+ return;
633
+ const rules = QUESTION_VALIDATION_RULES[question.questionTypeId ?? -1] ?? [];
634
+ for (const rule of rules) {
635
+ const message = rule(question);
636
+ if (!message)
637
+ continue;
638
+ ctx.addIssue({
639
+ code: "custom",
640
+ message,
641
+ path: ["options"]
642
+ });
643
+ }
644
+ }
645
+ var ZExerciseUpdateQuestion = ZExerciseUpdateQuestionBase.superRefine(validateQuestionOptions);
646
+ var ZExerciseCreate = z3.object({
647
+ title: z3.string().min(1),
648
+ description: z3.string().optional(),
649
+ lessonId: z3.string().optional(),
650
+ sectionId: z3.string().optional(),
651
+ order: z3.number().int().optional(),
652
+ courseId: z3.string().min(1),
653
+ dueBy: z3.string().optional(),
654
+ questions: z3.array(z3.object({
655
+ question: z3.string().min(1),
656
+ questionTypeId: z3.number().int().min(1).optional(),
657
+ points: z3.number().int().min(0).optional(),
658
+ settings: z3.record(z3.string(), z3.unknown()).optional(),
659
+ options: z3.array(z3.object({
660
+ label: z3.string().min(1),
661
+ isCorrect: z3.boolean(),
662
+ settings: z3.record(z3.string(), z3.unknown()).optional()
663
+ })).min(2)
664
+ })).optional()
665
+ });
666
+ var ZExerciseUpdate = z3.object({
667
+ title: z3.string().min(1).optional(),
668
+ description: z3.string().optional(),
669
+ lessonId: z3.string().optional(),
670
+ sectionId: z3.string().optional(),
671
+ order: z3.number().int().optional(),
672
+ isUnlocked: z3.boolean().optional(),
673
+ dueBy: z3.string().optional(),
674
+ // Changed from iso.datetime() to string to match frontend format
675
+ questions: z3.array(ZExerciseUpdateQuestion).optional()
676
+ });
677
+ var ZExerciseGetParam = z3.object({
678
+ exerciseId: z3.string().min(1)
679
+ });
680
+ var ZExerciseListQuery = z3.object({
681
+ lessonId: z3.string().optional(),
682
+ sectionId: z3.string().optional()
683
+ });
684
+ var ZExerciseSubmissionCreate = z3.object({
685
+ exerciseId: z3.string().min(1),
686
+ answers: z3.array(z3.object({
687
+ questionId: z3.number().int(),
688
+ optionId: z3.number().int().optional(),
689
+ answer: z3.string().optional()
690
+ })).min(1)
691
+ });
692
+ var ZExerciseFromTemplate = z3.object({
693
+ lessonId: z3.string().optional(),
694
+ sectionId: z3.string().optional(),
695
+ order: z3.number().int().optional(),
696
+ templateId: z3.number().int().min(1)
697
+ });
698
+
699
+ // ../utils/dist/validation/course-import/course-import.js
700
+ import * as z5 from "zod";
447
701
 
448
702
  // ../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()
703
+ import * as z4 from "zod";
704
+ var ZCourseClone = z4.object({
705
+ title: z4.string().min(1),
706
+ description: z4.string().optional(),
707
+ slug: z4.string().min(1),
708
+ organizationId: z4.string().min(1)
709
+ });
710
+ var ZCourseCloneParam = z4.object({
711
+ courseId: z4.string().min(1)
712
+ });
713
+ var ZCourseGetParam = z4.object({
714
+ courseId: z4.string().min(1)
715
+ });
716
+ var ZCourseGetQuery = z4.object({
717
+ slug: z4.string().optional()
718
+ });
719
+ var ZCourseGetBySlugParam = z4.object({
720
+ slug: z4.string().min(1)
721
+ });
722
+ var ZCourseEnrollParam = z4.object({
723
+ courseId: z4.string().min(1)
724
+ });
725
+ var ZCourseEnrollBody = z4.object({
726
+ inviteToken: z4.string().min(1).optional()
727
+ });
728
+ var ZCourseDownloadParam = z4.object({
729
+ courseId: z4.string().min(1)
730
+ });
731
+ var ZCertificateDownload = z4.object({
732
+ theme: z4.string().min(1),
733
+ studentName: z4.string().min(1),
734
+ courseName: z4.string().min(1),
735
+ courseDescription: z4.string().min(1),
736
+ orgName: z4.string().min(1),
737
+ orgLogoUrl: z4.string().url().optional(),
738
+ facilitator: z4.string().optional()
739
+ });
740
+ var ZCourseDownloadContent = z4.object({
741
+ courseTitle: z4.string().min(1),
742
+ orgName: z4.string().min(1),
743
+ orgTheme: z4.string().min(1),
744
+ lessons: z4.array(z4.object({
745
+ lessonTitle: z4.string().min(1),
746
+ lessonNumber: z4.string().min(1),
747
+ lessonNote: z4.string(),
748
+ slideUrl: z4.string().url().optional(),
749
+ video: z4.array(z4.string().url()).optional()
495
750
  }))
496
751
  });
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()
752
+ var ZLessonDownloadContent = z4.object({
753
+ title: z4.string().min(1),
754
+ number: z4.string().min(1),
755
+ orgName: z4.string().min(1),
756
+ note: z4.string(),
757
+ slideUrl: z4.string().url().optional(),
758
+ video: z4.array(z4.string().url()).optional(),
759
+ courseTitle: z4.string().min(1)
760
+ });
761
+ var ZCoursePresignUrlUpload = z4.object({
762
+ fileName: z4.string().min(1),
763
+ fileType: z4.enum(ALLOWED_CONTENT_TYPES)
764
+ });
765
+ var ZCourseDocumentPresignUrlUpload = z4.object({
766
+ fileName: z4.string().min(1),
767
+ fileType: z4.enum(ALLOWED_DOCUMENT_TYPES)
768
+ });
769
+ var ZCourseDownloadPresignedUrl = z4.object({
770
+ keys: z4.array(z4.string().min(1)).min(1)
771
+ });
772
+ var ZCourseContentUpdateItem = z4.object({
773
+ id: z4.string().min(1),
774
+ type: z4.enum(["LESSON", "EXERCISE"]),
775
+ isUnlocked: z4.boolean().optional(),
776
+ order: z4.number().int().min(0).optional(),
777
+ sectionId: z4.string().nullable().optional()
778
+ });
779
+ var ZCourseContentUpdate = z4.object({
780
+ items: z4.array(ZCourseContentUpdateItem).min(1)
781
+ });
782
+ var ZCourseContentDeleteItem = z4.object({
783
+ id: z4.string().min(1),
784
+ type: z4.enum(["LESSON", "EXERCISE"])
785
+ });
786
+ var ZCourseContentDelete = z4.object({
787
+ sectionId: z4.string().min(1).optional(),
788
+ items: z4.array(ZCourseContentDeleteItem).min(1).optional()
534
789
  }).refine((data) => Boolean(data.sectionId) !== Boolean(data.items), {
535
790
  message: "Provide either sectionId or items",
536
791
  path: ["sectionId"]
537
792
  });
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()
793
+ var ZCourseCreate = z4.object({
794
+ title: z4.string().min(1),
795
+ description: z4.string().min(1),
796
+ type: z4.enum(["LIVE_CLASS", "SELF_PACED"]),
797
+ organizationId: z4.string().min(1)
798
+ });
799
+ var ZCourseMetadata = z4.object({
800
+ requirements: z4.string().optional(),
801
+ description: z4.string().optional(),
802
+ goals: z4.string().optional(),
803
+ videoUrl: z4.string().optional(),
804
+ showDiscount: z4.boolean().optional(),
805
+ discount: z4.number().optional(),
806
+ paymentLink: z4.string().optional(),
807
+ reward: z4.object({
808
+ show: z4.boolean(),
809
+ description: z4.string()
555
810
  }).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()
811
+ instructor: z4.object({
812
+ name: z4.string(),
813
+ role: z4.string(),
814
+ coursesNo: z4.number(),
815
+ description: z4.string(),
816
+ imgUrl: z4.string()
562
817
  }).optional(),
563
- certificate: z2.object({
564
- templateUrl: z2.string()
818
+ certificate: z4.object({
819
+ templateUrl: z4.string()
565
820
  }).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()
821
+ reviews: z4.array(z4.object({
822
+ id: z4.number(),
823
+ hide: z4.boolean(),
824
+ name: z4.string(),
825
+ avatar_url: z4.string(),
826
+ rating: z4.number(),
827
+ created_at: z4.number(),
828
+ description: z4.string()
574
829
  })).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()
830
+ lessonTabsOrder: z4.array(z4.object({
831
+ id: z4.union([z4.literal(1), z4.literal(2), z4.literal(3), z4.literal(4)]),
832
+ name: z4.string()
578
833
  })).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(),
834
+ grading: z4.boolean().optional(),
835
+ lessonDownload: z4.boolean().optional(),
836
+ allowNewStudent: z4.boolean(),
837
+ sectionDisplay: z4.record(z4.string(), z4.boolean()).optional(),
838
+ isContentGroupingEnabled: z4.boolean().optional()
839
+ });
840
+ var ZCourseUpdate = z4.object({
841
+ title: z4.string().min(1).optional(),
842
+ description: z4.string().min(1).optional(),
843
+ type: z4.enum(["LIVE_CLASS", "SELF_PACED"]).optional(),
844
+ logo: z4.string().optional(),
845
+ slug: z4.string().optional(),
846
+ isPublished: z4.boolean().optional(),
847
+ overview: z4.string().optional(),
593
848
  metadata: ZCourseMetadata.optional(),
594
- isCertificateDownloadable: z2.boolean().optional(),
595
- certificateTheme: z2.string().optional(),
596
- tagIds: z2.array(z2.uuid()).max(100).optional()
849
+ isCertificateDownloadable: z4.boolean().optional(),
850
+ certificateTheme: z4.string().optional(),
851
+ tagIds: z4.array(z4.uuid()).max(100).optional()
597
852
  });
598
- var ZCourseUpdateParam = z2.object({
599
- courseId: z2.string().min(1)
853
+ var ZCourseUpdateParam = z4.object({
854
+ courseId: z4.string().min(1)
600
855
  });
601
- var ZCourseDeleteParam = z2.object({
602
- courseId: z2.string().min(1)
856
+ var ZCourseDeleteParam = z4.object({
857
+ courseId: z4.string().min(1)
603
858
  });
604
- var ZCourseProgressParam = z2.object({
605
- courseId: z2.string().min(1)
859
+ var ZCourseProgressParam = z4.object({
860
+ courseId: z4.string().min(1)
606
861
  });
607
- var ZCourseProgressQuery = z2.object({
608
- profileId: z2.string().uuid()
862
+ var ZCourseProgressQuery = z4.object({
863
+ profileId: z4.string().uuid()
609
864
  });
610
- var ZCourseUserAnalyticsParam = z2.object({
611
- courseId: z2.string().min(1),
612
- userId: z2.string().uuid()
865
+ var ZCourseUserAnalyticsParam = z4.object({
866
+ courseId: z4.string().min(1),
867
+ userId: z4.string().uuid()
613
868
  });
614
869
 
615
870
  // ../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"]),
871
+ var ZSupportedLocale = z5.enum(["en", "hi", "fr", "pt", "de", "vi", "ru", "es", "pl", "da"]);
872
+ var ZCourseImportWarning = z5.object({
873
+ code: z5.string().min(1),
874
+ message: z5.string().min(1),
875
+ severity: z5.enum(["info", "warning", "error"])
876
+ });
877
+ var ZCourseImportSourceReference = z5.object({
878
+ type: z5.enum(["prompt", "pdf", "course"]),
879
+ label: z5.string().min(1),
880
+ pageStart: z5.number().int().min(1).optional(),
881
+ pageEnd: z5.number().int().min(1).optional()
882
+ });
883
+ var ZCourseImportDraftCourse = z5.object({
884
+ title: z5.string().min(1),
885
+ description: z5.string().min(1),
886
+ type: z5.enum(["LIVE_CLASS", "SELF_PACED"]),
632
887
  locale: ZSupportedLocale.default("en"),
633
888
  metadata: ZCourseMetadata.optional()
634
889
  });
635
- var ZCourseImportDraftSection = z3.object({
636
- externalId: z3.string().min(1),
637
- title: z3.string().min(1),
638
- order: z3.number().int().min(0)
890
+ var ZCourseImportDraftSection = z5.object({
891
+ externalId: z5.string().min(1),
892
+ title: z5.string().min(1),
893
+ order: z5.number().int().min(0)
639
894
  });
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()
895
+ var ZCourseImportDraftLesson = z5.object({
896
+ externalId: z5.string().min(1),
897
+ sectionExternalId: z5.string().min(1),
898
+ title: z5.string().min(1),
899
+ order: z5.number().int().min(0),
900
+ isUnlocked: z5.boolean().optional(),
901
+ public: z5.boolean().optional()
647
902
  });
648
- var ZCourseImportDraftLessonLanguage = z3.object({
649
- lessonExternalId: z3.string().min(1),
903
+ var ZCourseImportDraftLessonLanguage = z5.object({
904
+ lessonExternalId: z5.string().min(1),
650
905
  locale: ZSupportedLocale,
651
- content: z3.string().min(1)
906
+ content: z5.string().min(1)
652
907
  });
653
- var ZCourseImportDraftPayload = z3.object({
908
+ var ZCourseImportDraftPayload = z5.object({
654
909
  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([])
910
+ tags: z5.array(z5.string().trim().min(1).max(80)).max(100).default([]),
911
+ sections: z5.array(ZCourseImportDraftSection).min(1),
912
+ lessons: z5.array(ZCourseImportDraftLesson).min(1),
913
+ lessonLanguages: z5.array(ZCourseImportDraftLessonLanguage).min(1),
914
+ exercises: z5.array(z5.record(z5.string(), z5.unknown())).optional(),
915
+ sourceReferences: z5.array(ZCourseImportSourceReference).optional(),
916
+ warnings: z5.array(ZCourseImportWarning).default([])
661
917
  }).superRefine((value, ctx) => {
918
+ const normalizedTags = /* @__PURE__ */ new Set();
919
+ value.tags.forEach((tag, index) => {
920
+ const tagKey = tag.trim().toLowerCase();
921
+ if (normalizedTags.has(tagKey)) {
922
+ ctx.addIssue({
923
+ code: z5.ZodIssueCode.custom,
924
+ path: ["tags", index],
925
+ message: "Draft tags must be unique"
926
+ });
927
+ }
928
+ normalizedTags.add(tagKey);
929
+ });
662
930
  const sectionIds = /* @__PURE__ */ new Set();
663
931
  value.sections.forEach((section, index) => {
664
932
  if (sectionIds.has(section.externalId)) {
665
933
  ctx.addIssue({
666
- code: z3.ZodIssueCode.custom,
934
+ code: z5.ZodIssueCode.custom,
667
935
  path: ["sections", index, "externalId"],
668
936
  message: "Section externalId must be unique"
669
937
  });
@@ -674,7 +942,7 @@ var ZCourseImportDraftPayload = z3.object({
674
942
  value.lessons.forEach((lesson, index) => {
675
943
  if (lessonIds.has(lesson.externalId)) {
676
944
  ctx.addIssue({
677
- code: z3.ZodIssueCode.custom,
945
+ code: z5.ZodIssueCode.custom,
678
946
  path: ["lessons", index, "externalId"],
679
947
  message: "Lesson externalId must be unique"
680
948
  });
@@ -682,7 +950,7 @@ var ZCourseImportDraftPayload = z3.object({
682
950
  lessonIds.add(lesson.externalId);
683
951
  if (!sectionIds.has(lesson.sectionExternalId)) {
684
952
  ctx.addIssue({
685
- code: z3.ZodIssueCode.custom,
953
+ code: z5.ZodIssueCode.custom,
686
954
  path: ["lessons", index, "sectionExternalId"],
687
955
  message: "Lesson sectionExternalId must reference an existing section"
688
956
  });
@@ -691,52 +959,76 @@ var ZCourseImportDraftPayload = z3.object({
691
959
  value.lessonLanguages.forEach((lessonLanguage, index) => {
692
960
  if (!lessonIds.has(lessonLanguage.lessonExternalId)) {
693
961
  ctx.addIssue({
694
- code: z3.ZodIssueCode.custom,
962
+ code: z5.ZodIssueCode.custom,
695
963
  path: ["lessonLanguages", index, "lessonExternalId"],
696
964
  message: "Lesson language must reference an existing lesson"
697
965
  });
698
966
  }
699
967
  });
700
968
  });
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(),
969
+ var ZCourseImportDraftCreate = z5.object({
970
+ sourceType: z5.enum(["prompt", "pdf", "course"]),
971
+ idempotencyKey: z5.string().min(1).optional(),
972
+ summary: z5.record(z5.string(), z5.unknown()).optional(),
973
+ sourceArtifacts: z5.array(z5.record(z5.string(), z5.unknown())).optional(),
706
974
  draft: ZCourseImportDraftPayload
707
975
  });
708
- var ZCourseImportCourseParam = z3.object({
709
- courseId: z3.string().min(1)
976
+ var ZCourseImportCourseParam = z5.object({
977
+ courseId: z5.string().min(1)
710
978
  });
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()
979
+ var ZCourseImportDraftCreateFromCourse = z5.object({
980
+ courseId: z5.string().min(1),
981
+ idempotencyKey: z5.string().min(1).optional(),
982
+ summary: z5.record(z5.string(), z5.unknown()).optional(),
983
+ sourceArtifacts: z5.array(z5.record(z5.string(), z5.unknown())).optional()
716
984
  });
717
- var ZCourseImportDraftGetParam = z3.object({
718
- draftId: z3.string().uuid()
985
+ var ZCourseImportDraftGetParam = z5.object({
986
+ draftId: z5.string().uuid()
719
987
  });
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(),
988
+ var ZCourseImportDraftUpdate = z5.object({
989
+ summary: z5.record(z5.string(), z5.unknown()).optional(),
990
+ sourceArtifacts: z5.array(z5.record(z5.string(), z5.unknown())).optional(),
991
+ warnings: z5.array(ZCourseImportWarning).optional(),
724
992
  draft: ZCourseImportDraftPayload.optional()
725
993
  });
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()
994
+ var ZCourseImportDraftPublish = z5.object({
995
+ title: z5.string().min(1).optional(),
996
+ description: z5.string().min(1).optional(),
997
+ type: z5.enum(["LIVE_CLASS", "SELF_PACED"]).optional(),
998
+ metadata: ZCourseMetadata.optional(),
999
+ bannerImageUrl: z5.string().url().optional(),
1000
+ bannerImageQuery: z5.string().min(1).max(120).optional(),
1001
+ generateBannerImage: z5.boolean().optional()
731
1002
  });
732
1003
  var ZCourseImportDraftPublishToCourse = ZCourseImportDraftPublish.extend({
733
- courseId: z3.string().min(1)
1004
+ courseId: z5.string().min(1)
734
1005
  });
735
1006
 
736
1007
  // src/tools/course-drafts.ts
737
1008
  var ZUpdateCourseDraftToolInput = ZCourseImportDraftUpdate.extend({
738
1009
  draftId: ZCourseImportDraftGetParam.shape.draftId
739
1010
  });
1011
+ var ZTagCourseDraftToolInput = ZAutomationDraftTagAssignment.extend({
1012
+ draftId: ZAutomationDraftTagParam.shape.draftId
1013
+ });
1014
+ var ZListCourseExercisesToolInput = ZExerciseListQuery.extend({
1015
+ courseId: ZCourseImportCourseParam.shape.courseId
1016
+ });
1017
+ var ZGetCourseExerciseToolInput = ZExerciseGetParam.extend({
1018
+ courseId: ZCourseImportCourseParam.shape.courseId
1019
+ });
1020
+ var ZCreateCourseExerciseToolInput = ZExerciseCreate.omit({
1021
+ courseId: true
1022
+ }).extend({
1023
+ courseId: ZCourseImportCourseParam.shape.courseId
1024
+ });
1025
+ var ZCreateCourseExerciseFromTemplateToolInput = ZExerciseFromTemplate.extend({
1026
+ courseId: ZCourseImportCourseParam.shape.courseId
1027
+ });
1028
+ var ZUpdateCourseExerciseToolInput = ZExerciseUpdate.extend({
1029
+ courseId: ZCourseImportCourseParam.shape.courseId,
1030
+ exerciseId: ZExerciseGetParam.shape.exerciseId
1031
+ });
740
1032
  var ZPublishCourseDraftToolInput = ZCourseImportDraftPublish.extend({
741
1033
  draftId: ZCourseImportDraftGetParam.shape.draftId
742
1034
  });
@@ -748,44 +1040,156 @@ var createCourseDraftFromCourseShape = ZCourseImportDraftCreateFromCourse.shape;
748
1040
  var getCourseStructureShape = ZCourseImportCourseParam.shape;
749
1041
  var getCourseDraftShape = ZCourseImportDraftGetParam.shape;
750
1042
  var updateCourseDraftShape = ZUpdateCourseDraftToolInput.shape;
1043
+ var tagCourseDraftShape = ZTagCourseDraftToolInput.shape;
1044
+ var listCourseExercisesShape = ZListCourseExercisesToolInput.shape;
1045
+ var getCourseExerciseShape = ZGetCourseExerciseToolInput.shape;
1046
+ var createCourseExerciseShape = ZCreateCourseExerciseToolInput.shape;
1047
+ var createCourseExerciseFromTemplateShape = ZCreateCourseExerciseFromTemplateToolInput.shape;
1048
+ var updateCourseExerciseShape = ZUpdateCourseExerciseToolInput.shape;
751
1049
  var publishCourseDraftShape = ZPublishCourseDraftToolInput.shape;
752
1050
  var publishCourseDraftToExistingCourseShape = ZPublishCourseDraftToExistingCourseToolInput.shape;
1051
+ var tagCoursesShape = ZAutomationCourseTagAssignment.shape;
753
1052
  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
- });
1053
+ server.tool(
1054
+ "get_course_structure",
1055
+ "Read the current live course structure. Use this before creating a fresh draft for post-publish edits.",
1056
+ getCourseStructureShape,
1057
+ async (args) => {
1058
+ const { courseId } = ZCourseImportCourseParam.parse(args);
1059
+ const result = await apiClient.getCourseStructure(courseId);
1060
+ return jsonContent(result);
1061
+ }
1062
+ );
1063
+ server.tool(
1064
+ "create_course_draft",
1065
+ "Create a new unpublished course draft from structured course JSON. Use this before the first publish.",
1066
+ createCourseDraftShape,
1067
+ async (args) => {
1068
+ const payload = ZCourseImportDraftCreate.parse(args);
1069
+ const result = await apiClient.createCourseDraft(payload);
1070
+ return jsonContent(result);
1071
+ }
1072
+ );
1073
+ server.tool(
1074
+ "create_course_draft_from_course",
1075
+ "Create a fresh draft from an existing live course. Use this after a course has already been published and the user wants more changes.",
1076
+ createCourseDraftFromCourseShape,
1077
+ async (args) => {
1078
+ const payload = ZCourseImportDraftCreateFromCourse.parse(args);
1079
+ const result = await apiClient.createCourseDraftFromCourse(payload);
1080
+ return jsonContent(result);
1081
+ }
1082
+ );
1083
+ server.tool(
1084
+ "get_course_draft",
1085
+ "Fetch an existing draft. Draft responses include status and publishedCourseId so you can decide whether to keep editing the draft or seed a fresh one from the live course.",
1086
+ getCourseDraftShape,
1087
+ async (args) => {
1088
+ const { draftId } = ZCourseImportDraftGetParam.parse(args);
1089
+ const result = await apiClient.getCourseDraft(draftId);
1090
+ return jsonContent(result);
1091
+ }
1092
+ );
1093
+ server.tool(
1094
+ "list_course_exercises",
1095
+ "List exercises for a live course, optionally filtered by lessonId or sectionId.",
1096
+ listCourseExercisesShape,
1097
+ async (args) => {
1098
+ const { courseId, ...query } = ZListCourseExercisesToolInput.parse(args);
1099
+ const result = await apiClient.listCourseExercises(courseId, query);
1100
+ return jsonContent(result);
1101
+ }
1102
+ );
1103
+ server.tool(
1104
+ "get_course_exercise",
1105
+ "Fetch a single exercise from a live course, including its questions and options.",
1106
+ getCourseExerciseShape,
1107
+ async (args) => {
1108
+ const { courseId, exerciseId } = ZGetCourseExerciseToolInput.parse(args);
1109
+ const result = await apiClient.getCourseExercise(courseId, exerciseId);
1110
+ return jsonContent(result);
1111
+ }
1112
+ );
1113
+ server.tool(
1114
+ "create_course_exercise",
1115
+ "Create a new exercise directly on a live course. Use this for adding exercises after a course has already been published.",
1116
+ createCourseExerciseShape,
1117
+ async (args) => {
1118
+ const { courseId, ...payload } = ZCreateCourseExerciseToolInput.parse(args);
1119
+ const result = await apiClient.createCourseExercise(courseId, payload);
1120
+ return jsonContent(result);
1121
+ }
1122
+ );
1123
+ server.tool(
1124
+ "create_course_exercise_from_template",
1125
+ "Create a new exercise on a live course from an existing template.",
1126
+ createCourseExerciseFromTemplateShape,
1127
+ async (args) => {
1128
+ const { courseId, ...payload } = ZCreateCourseExerciseFromTemplateToolInput.parse(args);
1129
+ const result = await apiClient.createCourseExerciseFromTemplate(courseId, payload);
1130
+ return jsonContent(result);
1131
+ }
1132
+ );
1133
+ server.tool(
1134
+ "update_course_exercise",
1135
+ "Update an existing exercise on a live course, including questions and options.",
1136
+ updateCourseExerciseShape,
1137
+ async (args) => {
1138
+ const { courseId, exerciseId, ...payload } = ZUpdateCourseExerciseToolInput.parse(args);
1139
+ const result = await apiClient.updateCourseExercise(courseId, exerciseId, payload);
1140
+ return jsonContent(result);
1141
+ }
1142
+ );
1143
+ server.tool(
1144
+ "update_course_draft",
1145
+ "Update an unpublished draft. If the draft is already published, do not keep editing it blindly; create a fresh draft from the live course first.",
1146
+ updateCourseDraftShape,
1147
+ async (args) => {
1148
+ const { draftId, ...payload } = ZUpdateCourseDraftToolInput.parse(args);
1149
+ const result = await apiClient.updateCourseDraft(draftId, payload);
1150
+ return jsonContent(result);
1151
+ }
1152
+ );
1153
+ server.tool(
1154
+ "tag_course_draft",
1155
+ "Attach human-readable tags to a draft before publish. The tags are stored on the draft and applied during publish.",
1156
+ tagCourseDraftShape,
1157
+ async (args) => {
1158
+ const { draftId, ...payload } = ZTagCourseDraftToolInput.parse(args);
1159
+ const result = await apiClient.tagCourseDraft(draftId, payload);
1160
+ return jsonContent(result);
1161
+ }
1162
+ );
1163
+ server.tool(
1164
+ "publish_course_draft",
1165
+ "Publish an unpublished draft as a new live course. The result includes courseId and courseUrl. After publish, future major edits should usually start from a fresh draft created from the live course.",
1166
+ publishCourseDraftShape,
1167
+ async (args) => {
1168
+ const { draftId, ...payload } = ZPublishCourseDraftToolInput.parse(args);
1169
+ const result = await apiClient.publishCourseDraft(draftId, payload);
1170
+ return jsonContent(result);
1171
+ }
1172
+ );
1173
+ server.tool(
1174
+ "publish_course_draft_to_existing_course",
1175
+ "Apply a fresh draft back onto an existing live course. Use this after reading the live course and creating a new draft from it.",
1176
+ publishCourseDraftToExistingCourseShape,
1177
+ async (args) => {
1178
+ const { draftId, ...payload } = ZPublishCourseDraftToExistingCourseToolInput.parse(args);
1179
+ const result = await apiClient.publishCourseDraftToExistingCourse(draftId, payload);
1180
+ return jsonContent(result);
1181
+ }
1182
+ );
1183
+ server.tool(
1184
+ "tag_courses",
1185
+ "Assign tags directly to one or more live courses by name. Default mode is merge, so existing tags are preserved unless replace is requested.",
1186
+ tagCoursesShape,
1187
+ async (args) => {
1188
+ const payload = ZAutomationCourseTagAssignment.parse(args);
1189
+ const result = await apiClient.tagCourses(payload);
1190
+ return jsonContent(result);
1191
+ }
1192
+ );
789
1193
  }
790
1194
  function jsonContent(data) {
791
1195
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@classroomio/mcp",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",