@classroomio/mcp 0.0.5 → 0.0.7

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 +56 -4
  2. package/dist/index.js +157 -39
  3. package/package.json +3 -2
package/README.md CHANGED
@@ -43,6 +43,7 @@ Current tools:
43
43
 
44
44
  - `list_org_courses`
45
45
  - `get_course_structure`
46
+ - `update_course_landing_page`
46
47
  - `create_course_draft`
47
48
  - `create_course_draft_from_course`
48
49
  - `get_course_draft`
@@ -163,6 +164,12 @@ Drafts support:
163
164
  - safer updates to existing courses
164
165
  - auditability of AI-generated structure
165
166
 
167
+ Lesson authoring rule:
168
+
169
+ - `lessonLanguages[].content` should contain only the lesson body HTML
170
+ - do not include the lesson title or a top-level heading that repeats `lessons[].title`
171
+ - ClassroomIO already renders the lesson title in the course UI
172
+
166
173
  ## After Publish
167
174
 
168
175
  Once a draft has been published, the live course becomes the safer source of truth.
@@ -215,7 +222,37 @@ Result:
215
222
  - publish returns the live `courseId` and public `courseUrl`
216
223
  - publish can optionally set a random Unsplash-derived course cover
217
224
 
218
- ### Flow 2: Create a draft from a PDF
225
+ ### Flow 2: Update a landing page with AI-generated copy and media
226
+
227
+ User says:
228
+
229
+ ```text
230
+ Rewrite this course landing page to feel more premium, generate a new cover image, and add three believable reviews.
231
+ ```
232
+
233
+ Expected tool sequence:
234
+
235
+ 1. Agent calls `list_org_courses` if it needs to resolve the course ID by name first.
236
+ 2. Agent calls `update_course_landing_page`.
237
+
238
+ What the landing-page tool can update:
239
+
240
+ - title
241
+ - short description
242
+ - overview
243
+ - requirements
244
+ - goals
245
+ - pricing fields
246
+ - instructor section
247
+ - reviews
248
+ - landing page image
249
+
250
+ The tool can either:
251
+
252
+ - use an explicit `imageUrl`
253
+ - or generate a random Unsplash-based image with `generateImage` and `imageQuery`
254
+
255
+ ### Flow 3: Create a draft from a PDF
219
256
 
220
257
  User says:
221
258
 
@@ -238,7 +275,7 @@ Important:
238
275
  - the PDF does not need to be uploaded to MCP
239
276
  - the MCP package only receives structured JSON
240
277
 
241
- ### Flow 3: Tag a draft before publish
278
+ ### Flow 4: Tag a draft before publish
242
279
 
243
280
  User says:
244
281
 
@@ -258,7 +295,7 @@ Result:
258
295
  - publish ensures the tags exist in ClassroomIO
259
296
  - publish assigns those tags to the live course
260
297
 
261
- ### Flow 4: Update an existing course safely
298
+ ### Flow 5: Update an existing course safely
262
299
 
263
300
  User says:
264
301
 
@@ -286,7 +323,7 @@ What this publish does today:
286
323
  - upserts lesson language content
287
324
  - merges draft tags into the existing course
288
325
 
289
- ### Flow 5: Add exercises to a live course
326
+ ### Flow 6: Add exercises to a live course
290
327
 
291
328
  User says:
292
329
 
@@ -351,6 +388,21 @@ Exercise tools use the live course directly:
351
388
  - `create_course_exercise_from_template`
352
389
  - `update_course_exercise`
353
390
 
391
+ Supported `questionTypeId` values for exercise payloads:
392
+
393
+ - `1` `RADIO` - Single answer
394
+ - `2` `CHECKBOX` - Multiple answers
395
+ - `3` `TEXTAREA` - Paragraph
396
+ - `4` `TRUE_FALSE` - True/False
397
+ - `5` `SHORT_ANSWER` - Short answer
398
+ - `6` `NUMERIC` - Numeric answer
399
+ - `7` `FILL_BLANK` - Fill in the blank
400
+ - `8` `FILE_UPLOAD` - File upload
401
+ - `9` `MATCHING` - Matching
402
+ - `10` `ORDERING` - Ordering
403
+ - `11` `HOTSPOT` - Hotspot
404
+ - `12` `LINK` - Links
405
+
354
406
  ## Operational Notes
355
407
 
356
408
  - `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
@@ -35,6 +35,12 @@ var ClassroomIoApiClient = class {
35
35
  method: "GET"
36
36
  });
37
37
  }
38
+ async updateCourseLandingPage(courseId, payload) {
39
+ return this.request(`/course/${courseId}/landing-page`, {
40
+ method: "PUT",
41
+ body: payload
42
+ });
43
+ }
38
44
  async getCourseDraft(draftId) {
39
45
  return this.request(`/organization/course-import/drafts/${draftId}`, {
40
46
  method: "GET"
@@ -108,13 +114,10 @@ var ClassroomIoApiClient = class {
108
114
  });
109
115
  }
110
116
  async tagCourses(payload) {
111
- return this.request(
112
- "/organization/tags/courses/assign",
113
- {
114
- method: "PUT",
115
- body: payload
116
- }
117
- );
117
+ return this.request("/organization/tags/courses/assign", {
118
+ method: "PUT",
119
+ body: payload
120
+ });
118
121
  }
119
122
  async request(path, options) {
120
123
  const response = await fetch(new URL(path, this.config.CLASSROOMIO_API_URL), {
@@ -443,6 +446,7 @@ var ZOrganizationApiKeyScope = z10.enum([
443
446
  "course_import:draft:update",
444
447
  "course_import:draft:publish",
445
448
  "course:read",
449
+ "course:write",
446
450
  "course:tag:write",
447
451
  "course:exercise:read",
448
452
  "course:exercise:write",
@@ -1051,6 +1055,33 @@ var ZCourseCreate = z12.object({
1051
1055
  type: z12.enum(["LIVE_CLASS", "SELF_PACED"]),
1052
1056
  organizationId: z12.string().min(1)
1053
1057
  });
1058
+ var ZCourseReward = z12.object({
1059
+ show: z12.boolean(),
1060
+ description: z12.string()
1061
+ });
1062
+ var ZCourseInstructor = z12.object({
1063
+ name: z12.string(),
1064
+ role: z12.string(),
1065
+ coursesNo: z12.number(),
1066
+ description: z12.string(),
1067
+ imgUrl: z12.string()
1068
+ });
1069
+ var ZCourseCertificate = z12.object({
1070
+ templateUrl: z12.string()
1071
+ });
1072
+ var ZCourseReview = z12.object({
1073
+ id: z12.number(),
1074
+ hide: z12.boolean(),
1075
+ name: z12.string(),
1076
+ avatar_url: z12.string(),
1077
+ rating: z12.number(),
1078
+ created_at: z12.number(),
1079
+ description: z12.string()
1080
+ });
1081
+ var ZCourseLessonTabsOrder = z12.array(z12.object({
1082
+ id: z12.union([z12.literal(1), z12.literal(2), z12.literal(3), z12.literal(4)]),
1083
+ name: z12.string()
1084
+ }));
1054
1085
  var ZCourseMetadata = z12.object({
1055
1086
  requirements: z12.string().optional(),
1056
1087
  description: z12.string().optional(),
@@ -1059,39 +1090,36 @@ var ZCourseMetadata = z12.object({
1059
1090
  showDiscount: z12.boolean().optional(),
1060
1091
  discount: z12.number().optional(),
1061
1092
  paymentLink: z12.string().optional(),
1062
- reward: z12.object({
1063
- show: z12.boolean(),
1064
- description: z12.string()
1065
- }).optional(),
1066
- instructor: z12.object({
1067
- name: z12.string(),
1068
- role: z12.string(),
1069
- coursesNo: z12.number(),
1070
- description: z12.string(),
1071
- imgUrl: z12.string()
1072
- }).optional(),
1073
- certificate: z12.object({
1074
- templateUrl: z12.string()
1075
- }).optional(),
1076
- reviews: z12.array(z12.object({
1077
- id: z12.number(),
1078
- hide: z12.boolean(),
1079
- name: z12.string(),
1080
- avatar_url: z12.string(),
1081
- rating: z12.number(),
1082
- created_at: z12.number(),
1083
- description: z12.string()
1084
- })).optional(),
1085
- lessonTabsOrder: z12.array(z12.object({
1086
- id: z12.union([z12.literal(1), z12.literal(2), z12.literal(3), z12.literal(4)]),
1087
- name: z12.string()
1088
- })).optional(),
1093
+ reward: ZCourseReward.optional(),
1094
+ instructor: ZCourseInstructor.optional(),
1095
+ certificate: ZCourseCertificate.optional(),
1096
+ reviews: z12.array(ZCourseReview).optional(),
1097
+ lessonTabsOrder: ZCourseLessonTabsOrder.optional(),
1089
1098
  grading: z12.boolean().optional(),
1090
1099
  lessonDownload: z12.boolean().optional(),
1091
1100
  allowNewStudent: z12.boolean(),
1092
1101
  sectionDisplay: z12.record(z12.string(), z12.boolean()).optional(),
1093
1102
  isContentGroupingEnabled: z12.boolean().optional()
1094
1103
  });
1104
+ var ZCourseLandingPageMetadataUpdate = z12.object({
1105
+ requirements: z12.string().optional(),
1106
+ description: z12.string().optional(),
1107
+ goals: z12.string().optional(),
1108
+ videoUrl: z12.string().optional(),
1109
+ showDiscount: z12.boolean().optional(),
1110
+ discount: z12.number().optional(),
1111
+ paymentLink: z12.string().optional(),
1112
+ reward: ZCourseReward.partial().optional(),
1113
+ instructor: ZCourseInstructor.partial().optional(),
1114
+ certificate: ZCourseCertificate.partial().optional(),
1115
+ reviews: z12.array(ZCourseReview).optional(),
1116
+ lessonTabsOrder: ZCourseLessonTabsOrder.optional(),
1117
+ grading: z12.boolean().optional(),
1118
+ lessonDownload: z12.boolean().optional(),
1119
+ allowNewStudent: z12.boolean().optional(),
1120
+ sectionDisplay: z12.record(z12.string(), z12.boolean()).optional(),
1121
+ isContentGroupingEnabled: z12.boolean().optional()
1122
+ });
1095
1123
  var ZCourseUpdate = z12.object({
1096
1124
  title: z12.string().min(1).optional(),
1097
1125
  description: z12.string().min(1).optional(),
@@ -1101,6 +1129,8 @@ var ZCourseUpdate = z12.object({
1101
1129
  isPublished: z12.boolean().optional(),
1102
1130
  overview: z12.string().optional(),
1103
1131
  metadata: ZCourseMetadata.optional(),
1132
+ cost: z12.number().int().min(0).optional(),
1133
+ currency: z12.enum(["NGN", "USD"]).optional(),
1104
1134
  isCertificateDownloadable: z12.boolean().optional(),
1105
1135
  certificateTheme: z12.string().optional(),
1106
1136
  tagIds: z12.array(z12.uuid()).max(100).optional()
@@ -1108,6 +1138,17 @@ var ZCourseUpdate = z12.object({
1108
1138
  var ZCourseUpdateParam = z12.object({
1109
1139
  courseId: z12.string().min(1)
1110
1140
  });
1141
+ var ZCourseLandingPageUpdate = z12.object({
1142
+ title: z12.string().min(1).optional(),
1143
+ description: z12.string().min(1).optional(),
1144
+ overview: z12.string().optional(),
1145
+ cost: z12.number().int().min(0).optional(),
1146
+ currency: z12.enum(["NGN", "USD"]).optional(),
1147
+ imageUrl: z12.url().optional(),
1148
+ generateImage: z12.boolean().optional(),
1149
+ imageQuery: z12.string().min(1).max(120).optional(),
1150
+ metadata: ZCourseLandingPageMetadataUpdate.optional()
1151
+ });
1111
1152
  var ZCourseDeleteParam = z12.object({
1112
1153
  courseId: z12.string().min(1)
1113
1154
  });
@@ -1124,6 +1165,7 @@ var ZCourseUserAnalyticsParam = z12.object({
1124
1165
 
1125
1166
  // ../utils/dist/validation/course-import/course-import.js
1126
1167
  var ZSupportedLocale = z13.enum(["en", "hi", "fr", "pt", "de", "vi", "ru", "es", "pl", "da"]);
1168
+ var LESSON_BODY_HTML_DESCRIPTION = "Lesson body HTML only. Do not include the lesson title or a top-level heading that repeats lessons[].title because the ClassroomIO UI already renders the lesson title separately.";
1127
1169
  var ZCourseImportWarning = z13.object({
1128
1170
  code: z13.string().min(1),
1129
1171
  message: z13.string().min(1),
@@ -1158,7 +1200,7 @@ var ZCourseImportDraftLesson = z13.object({
1158
1200
  var ZCourseImportDraftLessonLanguage = z13.object({
1159
1201
  lessonExternalId: z13.string().min(1),
1160
1202
  locale: ZSupportedLocale,
1161
- content: z13.string().min(1)
1203
+ content: z13.string().min(1).describe(LESSON_BODY_HTML_DESCRIPTION)
1162
1204
  });
1163
1205
  var ZCourseImportDraftPayload = z13.object({
1164
1206
  course: ZCourseImportDraftCourse,
@@ -1259,6 +1301,66 @@ var ZCourseImportDraftPublishToCourse = ZCourseImportDraftPublish.extend({
1259
1301
  courseId: z13.string().min(1)
1260
1302
  });
1261
1303
 
1304
+ // ../utils/dist/validation/course/section.js
1305
+ import * as z14 from "zod";
1306
+ var ZCourseSectionCreate = z14.object({
1307
+ title: z14.string().min(1),
1308
+ courseId: z14.string().min(1),
1309
+ order: z14.number().int().min(0).optional()
1310
+ });
1311
+ var ZCourseSectionUpdate = z14.object({
1312
+ title: z14.string().min(1).optional(),
1313
+ order: z14.number().int().min(0).optional()
1314
+ });
1315
+ var ZCourseSectionPromoteUngrouped = z14.object({
1316
+ title: z14.string().min(1)
1317
+ });
1318
+ var ZCourseSectionGetParam = z14.object({
1319
+ sectionId: z14.string().min(1)
1320
+ });
1321
+ var ZCourseSectionReorder = z14.object({
1322
+ sections: z14.array(z14.object({
1323
+ id: z14.string().min(1),
1324
+ order: z14.number().int().min(0)
1325
+ })).min(1)
1326
+ });
1327
+
1328
+ // ../utils/dist/validation/course/invite.js
1329
+ import * as z15 from "zod";
1330
+ var ZCourseInviteParam = z15.object({
1331
+ courseId: z15.string().min(1)
1332
+ });
1333
+ var ZCourseInviteRevokeParam = z15.object({
1334
+ courseId: z15.string().min(1),
1335
+ inviteId: z15.string().uuid()
1336
+ });
1337
+ var ZCourseInviteAuditParam = z15.object({
1338
+ courseId: z15.string().min(1),
1339
+ inviteId: z15.string().uuid()
1340
+ });
1341
+ var ZCourseInviteTokenParam = z15.object({
1342
+ token: z15.string().min(1)
1343
+ });
1344
+ var ZCreatePublicCourseInviteLink = z15.object({
1345
+ courseId: z15.uuid()
1346
+ });
1347
+ var ZCourseInvitePreset = z15.enum(["ONE_TIME_24H", "MULTI_USE_7D", "MULTI_USE_30D", "CUSTOM"]);
1348
+ var ZCreateCourseInvite = z15.object({
1349
+ preset: ZCourseInvitePreset.default("MULTI_USE_30D"),
1350
+ expiresAt: z15.string().min(1).optional(),
1351
+ maxUses: z15.number().int().min(1).max(1e3).optional(),
1352
+ allowedEmails: z15.array(z15.string().email()).max(100).optional(),
1353
+ allowedDomains: z15.array(z15.string().min(1)).max(100).optional(),
1354
+ recipientEmails: z15.array(z15.string().email()).max(500).optional(),
1355
+ recipientCsv: z15.string().max(25e3).optional(),
1356
+ sendEmail: z15.boolean().default(false),
1357
+ metadata: z15.record(z15.string(), z15.unknown()).optional()
1358
+ }).refine((data) => {
1359
+ const hasEmails = (data.recipientEmails?.length ?? 0) > 0;
1360
+ const hasCsv = typeof data.recipientCsv === "string" && data.recipientCsv.trim().length > 0;
1361
+ return hasEmails || hasCsv;
1362
+ }, { message: "recipientEmails or recipientCsv is required", path: ["recipientEmails"] });
1363
+
1262
1364
  // src/tools/course-drafts.ts
1263
1365
  var ZUpdateCourseDraftToolInput = ZCourseImportDraftUpdate.extend({
1264
1366
  draftId: ZCourseImportDraftGetParam.shape.draftId
@@ -1284,6 +1386,9 @@ var ZUpdateCourseExerciseToolInput = ZExerciseUpdate.extend({
1284
1386
  courseId: ZCourseImportCourseParam.shape.courseId,
1285
1387
  exerciseId: ZExerciseGetParam.shape.exerciseId
1286
1388
  });
1389
+ var ZUpdateCourseLandingPageToolInput = ZCourseLandingPageUpdate.extend({
1390
+ courseId: ZCourseImportCourseParam.shape.courseId
1391
+ });
1287
1392
  var ZPublishCourseDraftToolInput = ZCourseImportDraftPublish.extend({
1288
1393
  draftId: ZCourseImportDraftGetParam.shape.draftId
1289
1394
  });
@@ -1302,9 +1407,12 @@ var getCourseExerciseShape = ZGetCourseExerciseToolInput.shape;
1302
1407
  var createCourseExerciseShape = ZCreateCourseExerciseToolInput.shape;
1303
1408
  var createCourseExerciseFromTemplateShape = ZCreateCourseExerciseFromTemplateToolInput.shape;
1304
1409
  var updateCourseExerciseShape = ZUpdateCourseExerciseToolInput.shape;
1410
+ var updateCourseLandingPageShape = ZUpdateCourseLandingPageToolInput.shape;
1305
1411
  var publishCourseDraftShape = ZPublishCourseDraftToolInput.shape;
1306
1412
  var publishCourseDraftToExistingCourseShape = ZPublishCourseDraftToExistingCourseToolInput.shape;
1307
1413
  var tagCoursesShape = ZAutomationCourseTagAssignment.shape;
1414
+ var SUPPORTED_QUESTION_TYPES_GUIDE = "Supported questionTypeId values: RADIO=1 (Single answer), CHECKBOX=2 (Multiple answers), TEXTAREA=3 (Paragraph), TRUE_FALSE=4 (True/False), SHORT_ANSWER=5 (Short answer), NUMERIC=6 (Numeric answer), FILL_BLANK=7 (Fill in the blank), FILE_UPLOAD=8 (File upload), MATCHING=9 (Matching), ORDERING=10 (Ordering), HOTSPOT=11 (Hotspot), LINK=12 (Links). Use the numeric ids in exercise payloads.";
1415
+ var LESSON_HTML_GUIDE = "For draft lesson HTML, put only the lesson body in lessonLanguages[].content. Do not include the lesson title or a top-level heading that repeats lessons[].title because the ClassroomIO UI already renders the lesson title.";
1308
1416
  function registerCourseDraftTools(server, apiClient) {
1309
1417
  server.tool(
1310
1418
  "list_org_courses",
@@ -1326,9 +1434,19 @@ function registerCourseDraftTools(server, apiClient) {
1326
1434
  return jsonContent(result);
1327
1435
  }
1328
1436
  );
1437
+ server.tool(
1438
+ "update_course_landing_page",
1439
+ "Update landing-page-facing course fields on a live course, including headline copy, overview, requirements, goals, pricing, reviews, instructor information, and the course image. Use imageUrl to set an explicit cover image, or set generateImage/imageQuery to fetch a random Unsplash-based image.",
1440
+ updateCourseLandingPageShape,
1441
+ async (args) => {
1442
+ const { courseId, ...payload } = ZUpdateCourseLandingPageToolInput.parse(args);
1443
+ const result = await apiClient.updateCourseLandingPage(courseId, payload);
1444
+ return jsonContent(result);
1445
+ }
1446
+ );
1329
1447
  server.tool(
1330
1448
  "create_course_draft",
1331
- "Create a new unpublished course draft from structured course JSON. Use this before the first publish.",
1449
+ `Create a new unpublished course draft from structured course JSON. Use this before the first publish. ${LESSON_HTML_GUIDE}`,
1332
1450
  createCourseDraftShape,
1333
1451
  async (args) => {
1334
1452
  const payload = ZCourseImportDraftCreate.parse(args);
@@ -1378,7 +1496,7 @@ function registerCourseDraftTools(server, apiClient) {
1378
1496
  );
1379
1497
  server.tool(
1380
1498
  "create_course_exercise",
1381
- "Create a new exercise directly on a live course. Use this for adding exercises after a course has already been published.",
1499
+ `Create a new exercise directly on a live course. Use this for adding exercises after a course has already been published. ${SUPPORTED_QUESTION_TYPES_GUIDE}`,
1382
1500
  createCourseExerciseShape,
1383
1501
  async (args) => {
1384
1502
  const { courseId, ...payload } = ZCreateCourseExerciseToolInput.parse(args);
@@ -1398,7 +1516,7 @@ function registerCourseDraftTools(server, apiClient) {
1398
1516
  );
1399
1517
  server.tool(
1400
1518
  "update_course_exercise",
1401
- "Update an existing exercise on a live course, including questions and options.",
1519
+ `Update an existing exercise on a live course, including questions and options. ${SUPPORTED_QUESTION_TYPES_GUIDE}`,
1402
1520
  updateCourseExerciseShape,
1403
1521
  async (args) => {
1404
1522
  const { courseId, exerciseId, ...payload } = ZUpdateCourseExerciseToolInput.parse(args);
@@ -1408,7 +1526,7 @@ function registerCourseDraftTools(server, apiClient) {
1408
1526
  );
1409
1527
  server.tool(
1410
1528
  "update_course_draft",
1411
- "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.",
1529
+ `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. ${LESSON_HTML_GUIDE}`,
1412
1530
  updateCourseDraftShape,
1413
1531
  async (args) => {
1414
1532
  const { draftId, ...payload } = ZUpdateCourseDraftToolInput.parse(args);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@classroomio/mcp",
3
- "version": "0.0.5",
3
+ "version": "0.0.7",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -42,6 +42,7 @@
42
42
  "scripts": {
43
43
  "build": "tsc --noEmit && rm -rf dist tsconfig.tsbuildinfo && node ./scripts/build.mjs",
44
44
  "dev": "tsx src/index.ts",
45
- "start": "node dist/index.js"
45
+ "start": "node dist/index.js",
46
+ "format": "prettier --write \"src/**/*.ts\""
46
47
  }
47
48
  }