chore: update MCP package version to 0.0.7, add formatting script, and enhance README with architecture and principles
This commit is contained in:
@@ -1,9 +1,26 @@
|
||||
import {
|
||||
ZAutomationCourseTagAssignment,
|
||||
ZAutomationDraftTagAssignment,
|
||||
ZAutomationDraftTagParam
|
||||
} from '@cio/utils/validation/tag';
|
||||
import { ZGetOrganizationCoursesQuery } from '@cio/utils/validation/organization';
|
||||
import {
|
||||
ZExerciseCreate,
|
||||
ZExerciseFromTemplate,
|
||||
ZExerciseGetParam,
|
||||
ZExerciseListQuery,
|
||||
ZExerciseUpdate
|
||||
} from '@cio/utils/validation/exercise';
|
||||
import {
|
||||
ZCourseImportCourseParam,
|
||||
ZCourseImportDraftCreate,
|
||||
ZCourseImportDraftCreateFromCourse,
|
||||
ZCourseImportDraftGetParam,
|
||||
ZCourseImportDraftPublish,
|
||||
ZCourseImportDraftPublishToCourse,
|
||||
ZCourseImportDraftUpdate
|
||||
} from '@cio/utils/validation/course-import';
|
||||
import { ZCourseLandingPageUpdate } from '@cio/utils/validation/course';
|
||||
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
||||
import type { ZodRawShapeCompat } from '@modelcontextprotocol/sdk/server/zod-compat.js';
|
||||
|
||||
@@ -13,39 +30,245 @@ export const ZUpdateCourseDraftToolInput = ZCourseImportDraftUpdate.extend({
|
||||
draftId: ZCourseImportDraftGetParam.shape.draftId
|
||||
});
|
||||
|
||||
export const ZTagCourseDraftToolInput = ZAutomationDraftTagAssignment.extend({
|
||||
draftId: ZAutomationDraftTagParam.shape.draftId
|
||||
});
|
||||
|
||||
export const ZListCourseExercisesToolInput = ZExerciseListQuery.extend({
|
||||
courseId: ZCourseImportCourseParam.shape.courseId
|
||||
});
|
||||
|
||||
export const ZGetCourseExerciseToolInput = ZExerciseGetParam.extend({
|
||||
courseId: ZCourseImportCourseParam.shape.courseId
|
||||
});
|
||||
|
||||
export const ZCreateCourseExerciseToolInput = ZExerciseCreate.omit({
|
||||
courseId: true
|
||||
}).extend({
|
||||
courseId: ZCourseImportCourseParam.shape.courseId
|
||||
});
|
||||
|
||||
export const ZCreateCourseExerciseFromTemplateToolInput = ZExerciseFromTemplate.extend({
|
||||
courseId: ZCourseImportCourseParam.shape.courseId
|
||||
});
|
||||
|
||||
export const ZUpdateCourseExerciseToolInput = ZExerciseUpdate.extend({
|
||||
courseId: ZCourseImportCourseParam.shape.courseId,
|
||||
exerciseId: ZExerciseGetParam.shape.exerciseId
|
||||
});
|
||||
|
||||
export const ZUpdateCourseLandingPageToolInput = ZCourseLandingPageUpdate.extend({
|
||||
courseId: ZCourseImportCourseParam.shape.courseId
|
||||
});
|
||||
|
||||
export const ZPublishCourseDraftToolInput = ZCourseImportDraftPublish.extend({
|
||||
draftId: ZCourseImportDraftGetParam.shape.draftId
|
||||
});
|
||||
|
||||
export const ZPublishCourseDraftToExistingCourseToolInput = ZCourseImportDraftPublishToCourse.extend({
|
||||
draftId: ZCourseImportDraftGetParam.shape.draftId
|
||||
});
|
||||
|
||||
const createCourseDraftShape = ZCourseImportDraftCreate.shape as unknown as ZodRawShapeCompat;
|
||||
const createCourseDraftFromCourseShape = ZCourseImportDraftCreateFromCourse.shape as unknown as ZodRawShapeCompat;
|
||||
const listOrganizationCoursesShape = ZGetOrganizationCoursesQuery.shape as unknown as ZodRawShapeCompat;
|
||||
const getCourseStructureShape = ZCourseImportCourseParam.shape as unknown as ZodRawShapeCompat;
|
||||
const getCourseDraftShape = ZCourseImportDraftGetParam.shape as unknown as ZodRawShapeCompat;
|
||||
const updateCourseDraftShape = ZUpdateCourseDraftToolInput.shape as unknown as ZodRawShapeCompat;
|
||||
const tagCourseDraftShape = ZTagCourseDraftToolInput.shape as unknown as ZodRawShapeCompat;
|
||||
const listCourseExercisesShape = ZListCourseExercisesToolInput.shape as unknown as ZodRawShapeCompat;
|
||||
const getCourseExerciseShape = ZGetCourseExerciseToolInput.shape as unknown as ZodRawShapeCompat;
|
||||
const createCourseExerciseShape = ZCreateCourseExerciseToolInput.shape as unknown as ZodRawShapeCompat;
|
||||
const createCourseExerciseFromTemplateShape =
|
||||
ZCreateCourseExerciseFromTemplateToolInput.shape as unknown as ZodRawShapeCompat;
|
||||
const updateCourseExerciseShape = ZUpdateCourseExerciseToolInput.shape as unknown as ZodRawShapeCompat;
|
||||
const updateCourseLandingPageShape = ZUpdateCourseLandingPageToolInput.shape as unknown as ZodRawShapeCompat;
|
||||
const publishCourseDraftShape = ZPublishCourseDraftToolInput.shape as unknown as ZodRawShapeCompat;
|
||||
const publishCourseDraftToExistingCourseShape =
|
||||
ZPublishCourseDraftToExistingCourseToolInput.shape as unknown as ZodRawShapeCompat;
|
||||
const tagCoursesShape = ZAutomationCourseTagAssignment.shape as unknown as ZodRawShapeCompat;
|
||||
|
||||
const 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.';
|
||||
const 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.';
|
||||
|
||||
export function registerCourseDraftTools(server: McpServer, apiClient: ClassroomIoApiClient) {
|
||||
server.tool('create_course_draft', createCourseDraftShape, async (args) => {
|
||||
const payload = ZCourseImportDraftCreate.parse(args);
|
||||
const result = await apiClient.createCourseDraft(payload);
|
||||
return jsonContent(result);
|
||||
});
|
||||
server.tool(
|
||||
'list_org_courses',
|
||||
'List all courses in the authenticated organization. Use this when the user refers to a course by name and you need to discover the available course IDs first.',
|
||||
listOrganizationCoursesShape,
|
||||
async (args) => {
|
||||
const query = ZGetOrganizationCoursesQuery.parse(args);
|
||||
const result = await apiClient.listOrganizationCourses(query);
|
||||
return jsonContent(result);
|
||||
}
|
||||
);
|
||||
|
||||
server.tool('get_course_draft', getCourseDraftShape, async (args) => {
|
||||
const { draftId } = ZCourseImportDraftGetParam.parse(args);
|
||||
const result = await apiClient.getCourseDraft(draftId);
|
||||
return jsonContent(result);
|
||||
});
|
||||
server.tool(
|
||||
'get_course_structure',
|
||||
'Read the current live course structure. Use this before creating a fresh draft for post-publish edits.',
|
||||
getCourseStructureShape,
|
||||
async (args) => {
|
||||
const { courseId } = ZCourseImportCourseParam.parse(args);
|
||||
const result = await apiClient.getCourseStructure(courseId);
|
||||
return jsonContent(result);
|
||||
}
|
||||
);
|
||||
|
||||
server.tool('update_course_draft', updateCourseDraftShape, async (args) => {
|
||||
const { draftId, ...payload } = ZUpdateCourseDraftToolInput.parse(args);
|
||||
const result = await apiClient.updateCourseDraft(draftId, payload);
|
||||
return jsonContent(result);
|
||||
});
|
||||
server.tool(
|
||||
'update_course_landing_page',
|
||||
'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.',
|
||||
updateCourseLandingPageShape,
|
||||
async (args) => {
|
||||
const { courseId, ...payload } = ZUpdateCourseLandingPageToolInput.parse(args);
|
||||
const result = await apiClient.updateCourseLandingPage(courseId, payload);
|
||||
return jsonContent(result);
|
||||
}
|
||||
);
|
||||
|
||||
server.tool('publish_course_draft', publishCourseDraftShape, async (args) => {
|
||||
const { draftId, ...payload } = ZPublishCourseDraftToolInput.parse(args);
|
||||
const result = await apiClient.publishCourseDraft(draftId, payload);
|
||||
return jsonContent(result);
|
||||
});
|
||||
server.tool(
|
||||
'create_course_draft',
|
||||
`Create a new unpublished course draft from structured course JSON. Use this before the first publish. ${LESSON_HTML_GUIDE}`,
|
||||
createCourseDraftShape,
|
||||
async (args) => {
|
||||
const payload = ZCourseImportDraftCreate.parse(args);
|
||||
const result = await apiClient.createCourseDraft(payload);
|
||||
return jsonContent(result);
|
||||
}
|
||||
);
|
||||
|
||||
server.tool(
|
||||
'create_course_draft_from_course',
|
||||
'Create a fresh draft from an existing live course. Use this after a course has already been published and the user wants more changes.',
|
||||
createCourseDraftFromCourseShape,
|
||||
async (args) => {
|
||||
const payload = ZCourseImportDraftCreateFromCourse.parse(args);
|
||||
const result = await apiClient.createCourseDraftFromCourse(payload);
|
||||
return jsonContent(result);
|
||||
}
|
||||
);
|
||||
|
||||
server.tool(
|
||||
'get_course_draft',
|
||||
'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.',
|
||||
getCourseDraftShape,
|
||||
async (args) => {
|
||||
const { draftId } = ZCourseImportDraftGetParam.parse(args);
|
||||
const result = await apiClient.getCourseDraft(draftId);
|
||||
return jsonContent(result);
|
||||
}
|
||||
);
|
||||
|
||||
server.tool(
|
||||
'list_course_exercises',
|
||||
'List exercises for a live course, optionally filtered by lessonId or sectionId.',
|
||||
listCourseExercisesShape,
|
||||
async (args) => {
|
||||
const { courseId, ...query } = ZListCourseExercisesToolInput.parse(args);
|
||||
const result = await apiClient.listCourseExercises(courseId, query);
|
||||
return jsonContent(result);
|
||||
}
|
||||
);
|
||||
|
||||
server.tool(
|
||||
'get_course_exercise',
|
||||
'Fetch a single exercise from a live course, including its questions and options.',
|
||||
getCourseExerciseShape,
|
||||
async (args) => {
|
||||
const { courseId, exerciseId } = ZGetCourseExerciseToolInput.parse(args);
|
||||
const result = await apiClient.getCourseExercise(courseId, exerciseId);
|
||||
return jsonContent(result);
|
||||
}
|
||||
);
|
||||
|
||||
server.tool(
|
||||
'create_course_exercise',
|
||||
`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}`,
|
||||
createCourseExerciseShape,
|
||||
async (args) => {
|
||||
const { courseId, ...payload } = ZCreateCourseExerciseToolInput.parse(args);
|
||||
const result = await apiClient.createCourseExercise(courseId, payload);
|
||||
return jsonContent(result);
|
||||
}
|
||||
);
|
||||
|
||||
server.tool(
|
||||
'create_course_exercise_from_template',
|
||||
'Create a new exercise on a live course from an existing template.',
|
||||
createCourseExerciseFromTemplateShape,
|
||||
async (args) => {
|
||||
const { courseId, ...payload } = ZCreateCourseExerciseFromTemplateToolInput.parse(args);
|
||||
const result = await apiClient.createCourseExerciseFromTemplate(courseId, payload);
|
||||
return jsonContent(result);
|
||||
}
|
||||
);
|
||||
|
||||
server.tool(
|
||||
'update_course_exercise',
|
||||
`Update an existing exercise on a live course, including questions and options. ${SUPPORTED_QUESTION_TYPES_GUIDE}`,
|
||||
updateCourseExerciseShape,
|
||||
async (args) => {
|
||||
const { courseId, exerciseId, ...payload } = ZUpdateCourseExerciseToolInput.parse(args);
|
||||
const result = await apiClient.updateCourseExercise(courseId, exerciseId, payload);
|
||||
return jsonContent(result);
|
||||
}
|
||||
);
|
||||
|
||||
server.tool(
|
||||
'update_course_draft',
|
||||
`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}`,
|
||||
updateCourseDraftShape,
|
||||
async (args) => {
|
||||
const { draftId, ...payload } = ZUpdateCourseDraftToolInput.parse(args);
|
||||
const result = await apiClient.updateCourseDraft(draftId, payload);
|
||||
return jsonContent(result);
|
||||
}
|
||||
);
|
||||
|
||||
server.tool(
|
||||
'tag_course_draft',
|
||||
'Attach human-readable tags to a draft before publish. The tags are stored on the draft and applied during publish.',
|
||||
tagCourseDraftShape,
|
||||
async (args) => {
|
||||
const { draftId, ...payload } = ZTagCourseDraftToolInput.parse(args);
|
||||
const result = await apiClient.tagCourseDraft(draftId, payload);
|
||||
return jsonContent(result);
|
||||
}
|
||||
);
|
||||
|
||||
server.tool(
|
||||
'publish_course_draft',
|
||||
'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.',
|
||||
publishCourseDraftShape,
|
||||
async (args) => {
|
||||
const { draftId, ...payload } = ZPublishCourseDraftToolInput.parse(args);
|
||||
const result = await apiClient.publishCourseDraft(draftId, payload);
|
||||
return jsonContent(result);
|
||||
}
|
||||
);
|
||||
|
||||
server.tool(
|
||||
'publish_course_draft_to_existing_course',
|
||||
'Apply a fresh draft back onto an existing live course. Use this after reading the live course and creating a new draft from it.',
|
||||
publishCourseDraftToExistingCourseShape,
|
||||
async (args) => {
|
||||
const { draftId, ...payload } = ZPublishCourseDraftToExistingCourseToolInput.parse(args);
|
||||
const result = await apiClient.publishCourseDraftToExistingCourse(draftId, payload);
|
||||
return jsonContent(result);
|
||||
}
|
||||
);
|
||||
|
||||
server.tool(
|
||||
'tag_courses',
|
||||
'Assign tags directly to one or more live courses by name. Default mode is merge, so existing tags are preserved unless replace is requested.',
|
||||
tagCoursesShape,
|
||||
async (args) => {
|
||||
const payload = ZAutomationCourseTagAssignment.parse(args);
|
||||
const result = await apiClient.tagCourses(payload);
|
||||
return jsonContent(result);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function jsonContent(data: unknown) {
|
||||
|
||||
Reference in New Issue
Block a user