From beaubot
Create educational content (resources and courses) for the Beau platform. Use when the user wants to create resources, courses, quizzes, or upload images/PDFs for voice delivery.
npx claudepluginhub beau-education/beau-plugin --plugin beaubotThis skill uses the workspace's default tool permissions.
This skill helps you create educational resources for the Beau platform. Resources are the building blocks of your curriculum - each is a self-contained learning unit delivered by AI bots to students via voice conversation or presentation mode. Resources are grouped into courses.
Triggers research for existing libraries, tools, and patterns before coding new features. Searches npm, PyPI, MCP/skills, GitHub; evaluates matches and decides adopt/extend/build.
Audits cross-stack repos (C++/Android/iOS/Web), classifies files as project/third-party/artifacts, detects embedded libraries, assigns module verdicts, generates interactive HTML reports.
Reorganizes X and LinkedIn networks: review-first pruning of low-value follows, priority-based add/follow recommendations, and drafts warm outreach in user's voice.
Share bugs, ideas, or general feedback.
This skill helps you create educational resources for the Beau platform. Resources are the building blocks of your curriculum - each is a self-contained learning unit delivered by AI bots to students via voice conversation or presentation mode. Resources are grouped into courses.
When this skill is invoked, follow these steps:
Preflight Check: Verify the beaubot MCP server is connected by calling list_tags(pageSize: 1). If this fails with a tool error, tell the user: "The Beau MCP server is not connected. Please check your MCP connection and authenticate if prompted." Then stop.
Tag Governance (before creating anything):
a. Call list_tags(pageSize: 50) to fetch the full tag catalog
b. Reuse existing tags where possible — do not create near-duplicates (e.g. "maths" vs "math")
c. For genuinely new tags: ask the user to confirm, then call create_tag(name) to add them
d. Only use tags that exist in the catalog when calling create_resource
Gather Requirements: Ask the user about:
Design the Resource: Based on the guides, create a structure with:
Create Content: Draft the markdown content following best practices:
Source Media: Find or create visuals for the resource. Every resource should have images — they are essential for engagement, not optional decoration.
create_text_image for vocabulary cards, sight words, labels, math expressions — instant, no API key neededgenerate_image for custom educational illustrations — server-side AI generation, fast, no base64 overheadupload_image_from_url for existing public images (Wikimedia, educational sites)create_image (base64) only as a last resort for user-provided local filesExecute Creation: Use MCP tools to:
create_resourceupload_image_from_url or create_imagecreate_quizupdate_resource to embed image and quiz references using the correct markdown syntax (see below)Provide Testing Instructions: Tell the user how to test their resource using the Test Resource button in the admin UI
Use the beaubot MCP server tools:
| Tool | Description |
|---|---|
list_resources | List existing resources with optional tag filtering |
get_resource | Fetch a resource by ID |
create_resource | Create a new resource |
update_resource | Update an existing resource |
get_image | Download an image by ID — returns the image visually (for inspection) plus metadata |
generate_image | Generate an AI image from a text prompt and attach to a resource (uses org's OpenAI key, preferred) |
create_text_image | Render a word/phrase into a PNG image (no AI needed, instant) |
upload_image_from_url | Upload an image or PDF from a URL to a resource |
create_image | Upload an image or PDF (base64 data) to a resource (last resort) |
create_quiz | Create a quiz for a resource |
list_tags | List available tags in the organization's catalog |
create_tag | Create a new tag in the organization's tag catalog |
export_resource | Export a resource as a base64-encoded ZIP (includes all media and quizzes) |
import_resource | Import a resource from a base64-encoded ZIP (creates a new resource) |
create_course | Create a new course (collection of resources) |
list_courses | List existing courses |
add_resource_to_course | Add a resource to a course |
list_course_resources | List resources already in a course (with details) |
0. list_tags(pageSize: 50) → Fetch org's tag catalog
create_tag(name) → Create new tags (after user confirmation)
1. create_resource(name, content, tags, deliveryMode)
→ Returns resource with ID (tags must come from the catalog)
2. generate_image(resourceId, prompt, description, question, answer, hint, botVisible)
→ AI-generated image attached to resource (preferred for illustrations)
OR
create_text_image(resourceId, text, color, description, question, answer, hint, botVisible)
→ Renders word/phrase into PNG (preferred for vocabulary/labels)
OR
upload_image_from_url(resourceId, url, description, question, answer, hint, botVisible)
→ Downloads and attaches image from URL
OR (last resort)
create_image(resourceId, name, mimeType, data, description, question, answer, hint, botVisible)
→ Uploads base64-encoded image
3. create_quiz(resourceId, question, questionType, answers, ...)
→ Returns quiz with ID
4. update_resource(id, content)
→ CRITICAL: Update content to include image and quiz references
5. (Optional) create_course(name, description, tags, progressionType)
→ Returns course with ID
6. (Optional) list_course_resources(courseId)
→ View existing resources in a course before adding
7. (Optional) add_resource_to_course(courseId, resourceId, order)
→ Adds resource to the course
8. (Optional) export_resource(resourceId)
→ Returns base64-encoded ZIP for backup or sharing
9. (Optional) import_resource(zipData)
→ Creates a new resource from a previously exported ZIP
get_image:
imageId (required): The image ID to downloadimage/png, image/jpeg), returns the image visually plus a text label. For PDFs/videos, returns metadata only:
{
"id": 42,
"name": "dog-breeds.png",
"mimeType": "image/png",
"description": "Common dog breeds",
"question": "Can you identify these breeds?",
"answer": "Labrador, Poodle, German Shepherd",
"hint": "Look at the ear shapes",
"botVisible": true,
"resource": 15,
"organization": 3,
"createdAt": "2026-03-15T10:30:00.000Z",
"updatedAt": "2026-03-15T10:30:00.000Z"
}
create_resource:
name (required): Display name for the resourcecontent (required): Markdown contenttags (optional): Array of tags — must come from the tag catalog (call list_tags first, use create_tag for new ones)deliveryMode (optional): "conversation" or "presentation"generate_image (preferred for custom illustrations):
resourceId (required): Resource to attach the image toprompt (required): Text prompt describing the educational image to generate (max 4000 chars)description (optional): What the image shows (important for bot)question (optional): Question to ask about the imageanswer (optional): Expected answerhint (optional): Help for studentsbotVisible (optional): If true, bot can see the imagecreate_text_image (preferred for words/phrases):
resourceId (required): Resource to attach the image totext (required): The word or phrase to render (max 500 chars). Supports inline formatting: _underline_, *italic*, **underline+italic**, [highlight], {red:colored text}color (optional): Font color as CSS color (e.g. "#333333", "red"). Defaults to "#333333"includeLogo (optional): Include the organization logo at the top of the image. Defaults to truedescription (optional): What the image shows (important for bot). Auto-set from the text if not providedquestion (optional): Question to ask about the imageanswer (optional): Expected answerhint (optional): Help for studentsbotVisible (optional): If true, bot can see the imageupload_image_from_url (preferred for remote use):
resourceId (required): Resource to attach image tourl (required): Public URL of the image to downloadname (required): Image filenamedescription (optional): What the image shows (important for bot)question (optional): Question to ask about the imageanswer (optional): Expected answerhint (optional): Help for studentsbotVisible (optional): If true, bot can see the imagecreate_image:
resourceId (required): Resource to attach image toname (required): Image filenamemimeType (required): Only "image/png", "image/jpeg", or "application/pdf" are supporteddata (required): Base64-encoded image datadescription (optional): What the image shows (important for bot)question (optional): Question to ask about the imageanswer (optional): Expected answerhint (optional): Help for studentsbotVisible (optional): If true, bot can see the imagecreate_quiz:
resourceId (required): Resource to attach quiz toquestion (required): Question textquestionType (required): "single", "multiple", "freetext", "ordered_list", "matching", or "fill_in_blank"answers (for single/multiple/ordered_list): Array of {id, text, isCorrect}. For matching: {id, text, isCorrect, matchText}expectedAnswer (for freetext): Correct answerinputRestriction (for freetext): "text", "integer", "decimal", or "fraction"numericMin (optional): Minimum allowed value for numeric inputs (integer, decimal, fraction)numericMax (optional): Maximum allowed value for numeric inputs (integer, decimal, fraction)allowNegative (optional): Whether negative values are accepted (default: true)evaluationCriteria (optional): AI grading guidelinesretryLimit (optional): Max attemptshint (optional): Guidance for wrong answersdescription (optional): When the bot should present this quizcreate_course:
name (required): Course namedescription (optional): Course descriptiontags (optional): Array of tagsprogressionType (optional): "flexible", "sequential", or "random"openEnrollment (optional): If true, students can self-enroll from the course catalogdefaultTeacher (optional): User ID of the teacher assigned to self-enrolled students (required when openEnrollment is true)maxEnrollments (optional): Maximum number of students who can enroll (null = unlimited)priceCents (optional): Price in cents for paid courses (requires Stripe Connect)currency (optional): Currency code for paid courses (e.g., "GBP", "USD", "EUR")teacherOnly (optional): If true, course is only visible to teachers in the catalog (preview mode)export_resource:
resourceId (required): The resource ID to exportimport_resource:
zipData (required): Base64-encoded ZIP file data matching the format belowfileName (optional): Filename for the ZIP (default: "import.zip")The ZIP file must have all files at the root level (no subdirectory wrapper) with this structure:
manifest.json # Required: metadata and file references
content.md # Required: markdown content with portable tokens
media/ # Media files (images, videos, PDFs)
media-001.png
media-002.jpeg
quizzes/ # Quiz definitions
quiz-001.json
quiz-002.json
CRITICAL: Files must be at the ZIP root — NOT inside a subdirectory. A ZIP containing my-resource/manifest.json will fail; it must be just manifest.json.
{
"version": 1,
"exportedAt": "2026-01-01T00:00:00.000Z",
"resource": {
"name": "Subtraction on a Number Line",
"tags": ["maths", "subtraction"],
"deliveryMode": "conversation"
},
"media": [
{
"ref": "media-001",
"filename": "media/media-001.png",
"name": "Number Line Diagram",
"mimeType": "image/png",
"description": "A number line showing 15 - 3 = 12",
"question": "Where do we land after 3 hops back from 15?",
"answer": "We land on 12",
"hint": "Count the arrows backwards",
"order": null,
"botVisible": true
}
],
"quizzes": [
{
"ref": "quiz-001",
"filename": "quizzes/quiz-001.json"
}
]
}
Key rules:
version must be 1media and quizzes are arrays (not objects)ref field (e.g. "media-001") matching its portable token in content.mdref field (e.g. "quiz-001") matching its portable token in content.mdmimeType extension (e.g. media-001.png for image/png)image/png, image/jpeg, and application/pdf are supported for imagesContent must use portable media:// and quiz tokens (NOT database IDs):

::quiz{#quiz-001}
The importer replaces these tokens with real database IDs after creating the records.
Each quiz file (e.g. quizzes/quiz-001.json) contains:
{
"question": "What is 15 - 3?",
"questionType": "freetext",
"inputRestriction": "integer",
"expectedAnswer": "12",
"numericMin": 0,
"numericMax": 20,
"allowNegative": false,
"evaluationCriteria": "Accept only the number 12.",
"retryLimit": 3,
"hint": "Start at 15 and count back 3 hops.",
"description": "Quiz after the number line example."
}
create_tag:
name (required): Tag name to create (max 128 chars, lowercase recommended)list_course_resources:
courseId (required): The course ID to list resources foradd_resource_to_course:
courseId (required): Course IDresourceId (required): Resource ID to addorder (optional): Position in the course (important for sequential courses)bot (optional): Override the course's default bot for this specific resourcedeliveryMode (optional): Override the resource's default delivery mode within this courseCourses group related resources together for students to complete. When creating a course, consider:
| Type | Behavior | Best For |
|---|---|---|
| Flexible | Students complete resources in any order | Independent topics, reference materials |
| Sequential | Students must follow the specified order | Prerequisite content, building-block skills |
| Random | System selects the next resource | Practice drills, varied review |
order parameter in add_resource_to_courseEnable open enrollment to let students self-enroll from the course catalog:
defaultTeacher to be assigned for managing self-enrolled studentsmaxEnrollments to cap capacityteacherOnly: true to preview the course before releasing to studentsIf the organization has Stripe Connect configured:
priceCents and currency to create a paid courseAfter creating images and quizzes, you MUST update the resource content to include references to them. Without these references, the bot will NOT display the media or quizzes during delivery.

Example: If create_image returns { id: 156 }, insert:

::quiz{#QUIZ_ID}
Example: If create_quiz returns { id: 28 }, insert:
::quiz{#28}
# Step 1: Create resource
create_resource(
name: "Introduction to Photosynthesis",
content: "# Learning Objectives\n\nPlaceholder content...",
tags: ["science", "biology"],
deliveryMode: "conversation"
)
# Response: { id: 42, ... }
# Step 2: Upload image from URL
upload_image_from_url(
resourceId: 42,
url: "https://example.com/plant-cell.png",
name: "Plant Cell Diagram",
description: "Diagram of a plant cell showing chloroplasts",
question: "Where does photosynthesis occur?",
answer: "In the chloroplasts",
hint: "Look for the green organelles",
botVisible: true
)
# Response: { id: 156, ... }
# Step 3: Create quiz
create_quiz(
resourceId: 42,
description: "Test understanding of photosynthesis location",
question: "In which organelle does photosynthesis take place?",
questionType: "single",
answers: [
{ id: "a", text: "Mitochondria", isCorrect: false },
{ id: "b", text: "Chloroplast", isCorrect: true },
{ id: "c", text: "Nucleus", isCorrect: false }
],
retryLimit: 2,
hint: "It contains chlorophyll, which is green"
)
# Response: { id: 28, ... }
# Step 4: CRITICAL - Update resource with image and quiz references
update_resource(
id: 42,
content: "# Learning Objectives\n\nBy the end of this resource, the student should understand photosynthesis.\n\n## What is Photosynthesis?\n\nExplain that plants convert sunlight into energy. Show the diagram:\n\n\n\nDiscuss the diagram with the student.\n\n## Check Understanding\n\nNow test the student's knowledge:\n\n::quiz{#28}\n\n## Summary\n\nReview the key points."
)
{
"question": "What gas do plants absorb?",
"questionType": "single",
"answers": [
{ "id": "a", "text": "Oxygen", "isCorrect": false },
{ "id": "b", "text": "Carbon dioxide", "isCorrect": true }
],
"hint": "Think about what humans breathe out"
}
{
"question": "Select ALL ingredients for photosynthesis",
"questionType": "multiple",
"answers": [
{ "id": "a", "text": "Sunlight", "isCorrect": true },
{ "id": "b", "text": "Water", "isCorrect": true },
{ "id": "c", "text": "Oxygen", "isCorrect": false }
]
}
{
"question": "Calculate 15% of 80",
"questionType": "freetext",
"inputRestriction": "decimal",
"expectedAnswer": "12",
"evaluationCriteria": "Accept 12, 12.0, or 12.00"
}
{
"question": "What is 1/2 + 1/6?",
"questionType": "freetext",
"inputRestriction": "fraction",
"expectedAnswer": "2/3",
"numericMin": 0,
"numericMax": 1,
"allowNegative": false,
"evaluationCriteria": "Accept equivalent fractions (e.g. 4/6, 8/12)"
}
Students drag and drop items into the correct sequence. The answers array order defines the correct sequence — students see items shuffled.
{
"question": "Put these in order from smallest to largest",
"questionType": "ordered_list",
"answers": [
{ "id": "a", "text": "1/2", "isCorrect": false },
{ "id": "b", "text": "0.55", "isCorrect": false },
{ "id": "c", "text": "3/5", "isCorrect": false },
{ "id": "d", "text": "62%", "isCorrect": false }
],
"hint": "Convert everything to decimals to compare"
}
Note: isCorrect is ignored for ordered_list — the array order IS the correct answer. Minimum 2 items, maximum 10.
Students match items from two columns. The left column shows fixed terms, the right column shows shuffled matches that students drag to align correctly.
{
"question": "Match each part of speech to its example",
"questionType": "matching",
"answers": [
{ "id": "a", "text": "verb", "isCorrect": false, "matchText": "jump" },
{ "id": "b", "text": "noun", "isCorrect": false, "matchText": "book" },
{ "id": "c", "text": "adverb", "isCorrect": false, "matchText": "quickly" }
],
"hint": "Think about what each word does in a sentence"
}
Note: isCorrect is ignored for matching — correct state is when each answer's matchText is aligned next to its text. Each answer needs both text (term) and matchText (match). Minimum 2 pairs, maximum 10.
Students fill in missing words within a sentence. Supports free text input or word bank (dropdown) mode.
{
"question": "Water freezes at [] degrees Celsius and boils at [] degrees Celsius",
"questionType": "fill_in_blank",
"answers": [
{ "id": "a", "text": "0", "isCorrect": true },
{ "id": "b", "text": "100", "isCorrect": true }
],
"hint": "Think about the states of water"
}
For word bank mode (dropdown with distractors), set inputRestriction to "word_bank" and add distractor answers with isCorrect: false:
{
"question": "The [] sat on the []",
"questionType": "fill_in_blank",
"inputRestriction": "word_bank",
"answers": [
{ "id": "a", "text": "cat", "isCorrect": true },
{ "id": "b", "text": "mat", "isCorrect": true },
{ "id": "c", "text": "dog", "isCorrect": false },
{ "id": "d", "text": "hat", "isCorrect": false }
]
}
Note: Use [] in the question to mark blank positions. Correct answers (isCorrect: true) map in order to blanks. Distractors (isCorrect: false) appear as extra options in word bank mode. Minimum 1 blank.
NEVER generate images via Python/code execution (matplotlib, PIL, etc.) — this is extremely slow, produces poor results, and sends huge base64 payloads through MCP.
Instead, use these server-side tools in order of preference:
create_text_image — for words, phrases, vocabulary cards, sight words, labels, math expressions. Instant, no API key needed, deterministic rendering.generate_image — for custom educational illustrations, diagrams, scenes. Uses the org's OpenAI API key for server-side AI generation. Fast, no client-side overhead.upload_image_from_url — for publicly hosted images from Wikimedia, educational sites, etc.create_image (base64) — last resort only, for user-provided local files.Use get_image(imageId) to inspect every image before setting metadata. AI-generated images may not match your prompt exactly.
description, question, answer, and hint based on what the image ACTUALLY shows, not what you intendedBad example (metadata written from intent, not inspection):
generate_image(prompt: "A number line showing 5 + 3 = 8")
# Sets metadata WITHOUT viewing the result:
description: "Number line showing 5 + 3 = 8" # ← May not match actual image
Good example (metadata written after inspection):
generate_image(prompt: "A number line showing 5 + 3 = 8")
# Views the returned image, sees it actually shows a number line 0-10 with an arrow from 5 to 8
description: "A number line from 0 to 10 with an arrow jumping from 5 to 8"
question: "What number does the arrow land on?"
answer: "8"
Always call list_tags before assigning tags to resources. This prevents tag sprawl and keeps the organization's tag catalog clean.
create_tag# Step 1: Check existing tags
list_tags(pageSize: 50)
# → Items: [{ name: "maths" }, { name: "year-3" }, { name: "fractions" }]
# Step 2: User wants a resource tagged "maths", "year-3", "number-line"
# "maths" and "year-3" exist. "number-line" is new.
# → Ask user: "The tag 'number-line' doesn't exist yet. Shall I create it?"
# Step 3: User confirms
create_tag(name: "number-line")
# Step 4: Create resource with all tags from catalog
create_resource(name: "...", content: "...", tags: ["maths", "year-3", "number-line"])
#, ##, ### for pacing and quizzes need ::quiz{#ID} in the contentlist_tags before assigning tags - Reuse existing tags and only create new ones (via create_tag) with user confirmation.create_text_image for words/phrases, generate_image for custom illustrations, upload_image_from_url for public images.