Skip to main content
Glama
format-for-composer-v1.1.0.js22.7 kB
#!/usr/bin/env node /** * Format for Composer Tool v1.1.0 - FAIL-FAST RELIABILITY * Transforms validated lesson data into EXACT EuConquisto Composer JSON format with strict validation * @version 1.1.0 (January 20, 2025) * @status ENHANCED - Fail-fast validation with comprehensive error reporting * @reference JIT workflow step 5 of 7 * @enhancement Strict validation, no fallbacks, detailed troubleshooting */ export class ComposerFormatterV110 { constructor() { this.processingStartTime = null; this.transformationLog = []; this.validationErrors = []; this.supportedWidgetTypes = this.getSupportedWidgetTypes(); this.requiredComposerFields = ['version', 'metadata', 'interface', 'structure']; } /** * Main formatting entry point with FAIL-FAST validation * @param {Object} validatedLessonData - Validated lesson data with metadata and widgets * @returns {Object} Composer JSON format OR detailed error information */ async formatForComposer(validatedLessonData) { this.processingStartTime = Date.now(); this.transformationLog = []; this.validationErrors = []; try { console.error('[FORMAT_FOR_COMPOSER_V110] Starting fail-fast formatting transformation'); // FAIL-FAST VALIDATION PHASE const validationResult = this.validateValidatedLessonDataComprehensively(validatedLessonData); if (!validationResult.valid) { throw new Error(`Composer Formatting validation failed:\n\n${validationResult.errors.join('\n\n')}\n\nReceived input structure: ${JSON.stringify(this.getInputSummary(validatedLessonData), null, 2)}`); } const { metadata, widgets } = validationResult.normalizedData; console.error(`[FORMAT_FOR_COMPOSER_V110] Validation passed - Formatting lesson: "${metadata.topic}"`); console.error(`[FORMAT_FOR_COMPOSER_V110] Widgets to transform: ${widgets.length}`); // COMPOSER STRUCTURE CREATION PHASE const composition = this.createComposerStructure(metadata); // WIDGET TRANSFORMATION PHASE for (let index = 0; index < widgets.length; index++) { const widget = widgets[index]; console.error(`[FORMAT_FOR_COMPOSER_V110] Transforming widget ${index + 1}/${widgets.length}: ${widget.type}`); const transformedWidget = this.transformWidgetStrict(widget, index, widgets.length, metadata); if (transformedWidget) { composition.structure.push(transformedWidget); this.logTransformation(`WIDGET_${index + 1}`, widget.type, 'transformed successfully'); } else { throw new Error(`Failed to transform widget ${index + 1} (${widget.type}). Transformation returned null.`); } } // FINAL VALIDATION PHASE this.validateComposerOutputStrict(composition); const processingTime = Date.now() - this.processingStartTime; console.error(`[FORMAT_FOR_COMPOSER_V110] Success - ${composition.structure.length} widgets transformed in ${processingTime}ms`); return { success: true, data: { composerJSON: composition, transformationSummary: { totalWidgets: composition.structure.length, processingTime: processingTime, formatCompliance: "EuConquisto Composer API v1.1", readyForSave: true }, qualityMetrics: { transformationSuccess: true, widgetIntegrity: "100%", formatValidation: "passed" }, nextStepGuidance: { readyForSave: true, saveTool: "save_composition_direct_api", note: "Composer JSON is fully formatted and ready for API save" } }, metadata: { version: "1.1.0", timestamp: new Date().toISOString(), processingTime: processingTime, failFastValidation: "passed", widgetsTransformed: composition.structure.length } }; } catch (error) { console.error('[FORMAT_FOR_COMPOSER_V110] FAIL-FAST ERROR:', error.message); return { success: false, error: { code: 'COMPOSER_FORMATTING_FAILED', message: error.message, timestamp: new Date().toISOString(), processingTime: Date.now() - this.processingStartTime, failFast: true, developmentMode: true, troubleshooting: { requiredInputStructure: { validatedLessonData: { metadata: { topic: "string - lesson title", gradeLevel: "string - target grade level", subject: "string - subject area", duration: "string (optional)" }, widgets: [ { id: "string - unique identifier", type: "string - supported widget type", content: "object - widget-specific content" } ] } }, transformationPhases: [ "1. Input validation and normalization", "2. Composer structure creation", "3. Widget-by-widget transformation", "4. Final Composer JSON validation", "5. Output quality verification" ], supportedWidgetTypes: this.supportedWidgetTypes, commonIssues: [ "Invalid widget types not supported by Composer", "Missing or malformed widget content", "Metadata fields missing or invalid", "Widget transformation errors", "Composer JSON structure validation failures" ], debugSteps: [ "1. Check if validatedLessonData is properly structured", "2. Verify all widget types are supported", "3. Ensure widget content follows expected format", "4. Check metadata completeness", "5. Validate transformation output structure" ] } } }; } } /** * COMPREHENSIVE FAIL-FAST VALIDATION for validated lesson data */ validateValidatedLessonDataComprehensively(validatedLessonData) { const errors = []; // Basic input validation if (!validatedLessonData) { errors.push("❌ MISSING INPUT ERROR:\nvalidatedLessonData is required.\nThis should come from the validate_lesson_data tool output.\nExpected: { metadata: {...}, widgets: [...] }"); return { valid: false, errors }; } if (typeof validatedLessonData !== 'object' || validatedLessonData === null) { errors.push(`❌ INPUT TYPE ERROR:\nvalidatedLessonData must be an object, received: ${typeof validatedLessonData}\nValue: ${JSON.stringify(validatedLessonData, null, 2)}`); return { valid: false, errors }; } // Handle nested data structure (from validate_lesson_data output) let actualData = validatedLessonData; if (validatedLessonData.data && validatedLessonData.data.validatedLessonData) { console.error('[FORMAT_FOR_COMPOSER_V110] Extracting data from nested structure'); actualData = validatedLessonData.data.validatedLessonData; } else if (validatedLessonData.validatedLessonData) { console.error('[FORMAT_FOR_COMPOSER_V110] Extracting data from wrapped structure'); actualData = validatedLessonData.validatedLessonData; } // Metadata validation if (!actualData.metadata) { errors.push("❌ MISSING METADATA ERROR:\nmetadata object is required in validated lesson data.\nMetadata should contain lesson information (topic, gradeLevel, subject).\nCheck validate_lesson_data tool output."); return { valid: false, errors }; } if (typeof actualData.metadata !== 'object' || actualData.metadata === null) { errors.push(`❌ METADATA TYPE ERROR:\nmetadata must be an object, received: ${typeof actualData.metadata}\nValue: ${JSON.stringify(actualData.metadata)}`); return { valid: false, errors }; } // Required metadata fields const requiredMetadataFields = ['topic', 'gradeLevel', 'subject']; const missingMetadataFields = requiredMetadataFields.filter(field => !actualData.metadata[field]); if (missingMetadataFields.length > 0) { errors.push(`❌ INCOMPLETE METADATA ERROR:\nRequired metadata fields missing: ${missingMetadataFields.join(', ')}\nRequired fields: ${requiredMetadataFields.join(', ')}\nReceived metadata: ${JSON.stringify(actualData.metadata, null, 2)}`); return { valid: false, errors }; } // Widgets validation if (!actualData.widgets) { errors.push("❌ MISSING WIDGETS ERROR:\nwidgets array is required in validated lesson data.\nWidgets should contain the lesson content structure.\nCheck validate_lesson_data tool output."); return { valid: false, errors }; } if (!Array.isArray(actualData.widgets)) { errors.push(`❌ WIDGETS TYPE ERROR:\nwidgets must be an array, received: ${typeof actualData.widgets}\nValue: ${JSON.stringify(actualData.widgets)}`); return { valid: false, errors }; } if (actualData.widgets.length === 0) { errors.push("❌ EMPTY WIDGETS ERROR:\nwidgets array cannot be empty.\nAt least one widget is required for Composer formatting.\nEnsure validate_lesson_data completed successfully."); return { valid: false, errors }; } // Individual widget validation actualData.widgets.forEach((widget, index) => { const widgetErrors = this.validateWidgetForTransformation(widget, index); if (widgetErrors.length > 0) { errors.push(`❌ WIDGET ${index + 1} TRANSFORMATION ERROR:\n${widgetErrors.join('\n')}\nWidget: ${JSON.stringify(widget, null, 2)}`); } }); if (errors.length > 0) { return { valid: false, errors }; } console.error(`[FORMAT_FOR_COMPOSER_V110] Validation passed - normalized data extracted`); return { valid: true, normalizedData: { metadata: actualData.metadata, widgets: actualData.widgets }, errors: [] }; } /** * VALIDATE widget for transformation readiness */ validateWidgetForTransformation(widget, index) { const errors = []; // Basic structure if (!widget || typeof widget !== 'object') { errors.push(`Widget must be an object, received: ${typeof widget}`); return errors; } // Required fields if (!widget.id || typeof widget.id !== 'string' || widget.id.trim().length === 0) { errors.push(`Widget ID is required and must be a non-empty string`); } if (!widget.type || typeof widget.type !== 'string') { errors.push(`Widget type is required and must be a string`); } else if (!this.supportedWidgetTypes.includes(widget.type)) { errors.push(`Unsupported widget type for Composer: ${widget.type}\nSupported types: ${this.supportedWidgetTypes.join(', ')}`); } if (!widget.content || typeof widget.content !== 'object' || widget.content === null) { errors.push(`Widget content is required and must be an object`); } return errors; } /** * CREATE Composer structure with metadata and interface */ createComposerStructure(metadata) { const composition = { version: "1.1", metadata: { title: metadata.topic || "Educational Lesson", description: metadata.description || `Lesson about ${metadata.topic} for ${metadata.gradeLevel}`, thumb: metadata.thumb || null, tags: metadata.tags || [metadata.subject, metadata.gradeLevel].filter(Boolean) }, interface: { content_language: "pt_br", index_option: "buttons", font_family: "Lato", show_summary: "disabled", finish_btn: "disabled" }, structure: [] }; console.error(`[FORMAT_FOR_COMPOSER_V110] Composer structure created for: "${metadata.topic}"`); return composition; } /** * STRICT widget transformation with comprehensive error handling */ transformWidgetStrict(widget, index, totalWidgets, metadata) { try { const baseType = widget.type.split('-')[0]; let transformedWidget = null; switch (baseType) { case 'head': transformedWidget = this.transformHeadWidget(widget, index); break; case 'text': transformedWidget = this.transformTextWidget(widget, index); break; case 'quiz': transformedWidget = this.transformQuizWidget(widget, index); break; case 'flashcards': transformedWidget = this.transformFlashcardsWidget(widget, index); break; case 'image': transformedWidget = this.transformImageWidget(widget, index); break; case 'list': transformedWidget = this.transformListWidget(widget, index); break; case 'video': transformedWidget = this.transformVideoWidget(widget, index); break; case 'hotspots': transformedWidget = this.transformHotspotsWidget(widget, index); break; default: throw new Error(`Unsupported widget type for transformation: ${widget.type}`); } if (!transformedWidget) { throw new Error(`Transformation function returned null for widget type: ${widget.type}`); } // Validate transformed widget structure this.validateTransformedWidget(transformedWidget, widget.type); return transformedWidget; } catch (error) { throw new Error(`Widget transformation failed for ${widget.type} at index ${index}: ${error.message}`); } } /** * VALIDATE transformed widget structure */ validateTransformedWidget(transformedWidget, originalType) { if (!transformedWidget || typeof transformedWidget !== 'object') { throw new Error(`Transformed widget is not a valid object for type: ${originalType}`); } if (!transformedWidget.id || typeof transformedWidget.id !== 'string') { throw new Error(`Transformed widget missing valid ID for type: ${originalType}`); } if (!transformedWidget.type || typeof transformedWidget.type !== 'string') { throw new Error(`Transformed widget missing valid type for type: ${originalType}`); } if (!transformedWidget.content || typeof transformedWidget.content !== 'object') { throw new Error(`Transformed widget missing valid content for type: ${originalType}`); } } /** * Transform head widget to Composer format */ transformHeadWidget(widget, index) { if (!widget.content.title) { throw new Error("Head widget content.title is required"); } return { id: widget.id, type: "head-1", content: { title: widget.content.title, subtitle: widget.content.subtitle || "" } }; } /** * Transform text widget to Composer format */ transformTextWidget(widget, index) { if (!widget.content.text) { throw new Error("Text widget content.text is required"); } return { id: widget.id, type: "text-1", content: { text: widget.content.text, title: widget.content.title || "" } }; } /** * Transform quiz widget to Composer format */ transformQuizWidget(widget, index) { if (!widget.content.questions || !Array.isArray(widget.content.questions)) { throw new Error("Quiz widget content.questions array is required"); } if (widget.content.questions.length === 0) { throw new Error("Quiz widget must have at least one question"); } const transformedQuestions = widget.content.questions.map((q, qIndex) => { if (!q.question || !q.options || !Array.isArray(q.options) || typeof q.correct !== 'number') { throw new Error(`Quiz question ${qIndex + 1} missing required fields (question, options, correct)`); } return { question: q.question, options: q.options, correct: q.correct }; }); return { id: widget.id, type: "quiz-1", content: { questions: transformedQuestions } }; } /** * Transform flashcards widget to Composer format */ transformFlashcardsWidget(widget, index) { if (!widget.content.cards || !Array.isArray(widget.content.cards)) { throw new Error("Flashcards widget content.cards array is required"); } if (widget.content.cards.length === 0) { throw new Error("Flashcards widget must have at least one card"); } const transformedCards = widget.content.cards.map((card, cIndex) => { if (!card.front || !card.back) { throw new Error(`Flashcard ${cIndex + 1} missing required fields (front, back)`); } return { front: card.front, back: card.back }; }); return { id: widget.id, type: "flashcards-1", content: { cards: transformedCards } }; } /** * Transform image widget to Composer format */ transformImageWidget(widget, index) { if (!widget.content.src) { throw new Error("Image widget content.src is required"); } return { id: widget.id, type: "image-1", content: { src: widget.content.src, alt: widget.content.alt || "Educational image", caption: widget.content.caption || "" } }; } /** * Transform list widget to Composer format */ transformListWidget(widget, index) { if (!widget.content.items || !Array.isArray(widget.content.items)) { throw new Error("List widget content.items array is required"); } if (widget.content.items.length === 0) { throw new Error("List widget must have at least one item"); } return { id: widget.id, type: "list-1", content: { items: widget.content.items, title: widget.content.title || "" } }; } /** * Transform video widget to Composer format */ transformVideoWidget(widget, index) { if (!widget.content.src) { throw new Error("Video widget content.src is required"); } return { id: widget.id, type: "video-1", content: { src: widget.content.src, title: widget.content.title || "", description: widget.content.description || "" } }; } /** * Transform hotspots widget to Composer format */ transformHotspotsWidget(widget, index) { if (!widget.content.image || !widget.content.hotspots) { throw new Error("Hotspots widget requires content.image and content.hotspots"); } return { id: widget.id, type: "hotspots-1", content: { image: widget.content.image, hotspots: widget.content.hotspots } }; } /** * VALIDATE final Composer output structure */ validateComposerOutputStrict(composition) { const errors = []; // Check required fields this.requiredComposerFields.forEach(field => { if (!composition[field]) { errors.push(`Composer JSON missing required field: ${field}`); } }); // Validate structure array if (!Array.isArray(composition.structure)) { errors.push("Composer JSON structure must be an array"); } else if (composition.structure.length === 0) { errors.push("Composer JSON structure cannot be empty"); } // Validate each widget in structure composition.structure.forEach((widget, index) => { if (!widget.id || !widget.type || !widget.content) { errors.push(`Widget ${index + 1} in structure missing required fields (id, type, content)`); } }); if (errors.length > 0) { throw new Error(`Composer JSON validation failed:\n${errors.join('\n')}`); } console.error(`[FORMAT_FOR_COMPOSER_V110] Final Composer JSON validation passed`); } /** * Log transformation step */ logTransformation(widgetId, widgetType, result) { this.transformationLog.push({ widgetId, widgetType, result, timestamp: new Date().toISOString() }); } /** * Get input summary for error reporting */ getInputSummary(input) { if (!input) return "null"; if (typeof input !== 'object') return typeof input; const summary = { hasMetadata: !!input.metadata, hasWidgets: !!input.widgets, hasNestedData: !!(input.data || input.validatedLessonData) }; if (input.metadata) { summary.metadataKeys = Object.keys(input.metadata); } if (input.widgets) { summary.widgetsCount = Array.isArray(input.widgets) ? input.widgets.length : 'not array'; } return summary; } /** * Get supported widget types for Composer */ getSupportedWidgetTypes() { return [ 'head-1', 'head-2', 'text-1', 'text-2', 'quiz-1', 'quiz-2', 'flashcards-1', 'flashcards-2', 'image-1', 'image-2', 'video-1', 'video-2', 'list-1', 'list-2', 'hotspots-1', 'hotspots-2' ]; } } /** * Create and export the tool instance for JIT server integration */ export function createComposerFormatterV110() { const formatter = new ComposerFormatterV110(); return { name: 'format_for_composer_v110', description: 'STEP 5 v1.1.0: Transform validated lesson data into exact EuConquisto Composer JSON format with fail-fast validation. Ensures perfect API compliance with detailed error reporting.', inputSchema: { type: 'object', properties: { validatedLessonData: { type: 'object', description: 'Validated lesson data from validate_lesson_data tool output', properties: { metadata: { type: 'object', properties: { topic: { type: 'string' }, gradeLevel: { type: 'string' }, subject: { type: 'string' } }, required: ['topic', 'gradeLevel', 'subject'] }, widgets: { type: 'array', items: { type: 'object', properties: { id: { type: 'string' }, type: { type: 'string' }, content: { type: 'object' } }, required: ['id', 'type', 'content'] } } }, required: ['metadata', 'widgets'] } }, required: ['validatedLessonData'] }, handler: async (input) => { return await formatter.formatForComposer(input.validatedLessonData); } }; }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/rkm097git/euconquisto-composer-mcp-poc'

If you have feedback or need assistance with the MCP directory API, please join our Discord server