Skip to main content
Glama
get-widget-requirements-v1.1.0.js24.2 kB
#!/usr/bin/env node /** * Widget Requirements Tool v1.1.0 - FAIL-FAST RELIABILITY * Provides precise API requirements for selected widgets with strict validation * @version 1.1.0 (January 20, 2025) * @status ENHANCED - Fail-fast validation with comprehensive error reporting * @reference JIT workflow step 3 of 7 * @enhancement Strict validation, no fallbacks, detailed troubleshooting */ export class WidgetRequirementsToolV110 { constructor() { this.processingStartTime = null; this.validationErrors = []; this.requirementsDatabase = this.initializeRequirementsDatabase(); this.supportedWidgetTypes = this.getSupportedWidgetTypes(); } /** * Main requirements entry point with FAIL-FAST validation * @param {Object} input - Contains selectedWidgets (array or object with widget types) * @returns {Object} Precise API requirements for selected widgets OR detailed error */ async getWidgetRequirements(input) { this.processingStartTime = new Date(); this.validationErrors = []; try { console.error('[WIDGET_REQUIREMENTS_V110] Starting fail-fast validation'); // FAIL-FAST VALIDATION PHASE const validationResult = this.validateSelectedWidgetsComprehensively(input); if (!validationResult.valid) { throw new Error(`Widget Requirements validation failed:\n\n${validationResult.errors.join('\n\n')}\n\nReceived input: ${JSON.stringify(input, null, 2)}`); } const { normalizedWidgetTypes } = validationResult; console.error(`[WIDGET_REQUIREMENTS_V110] Validation passed - Processing ${normalizedWidgetTypes.length} widget types`); console.error(`[WIDGET_REQUIREMENTS_V110] Widget types: ${normalizedWidgetTypes.join(', ')}`); // REQUIREMENTS GENERATION PHASE const specificRequirements = this.getSpecificRequirements(normalizedWidgetTypes); const targetedExamples = this.generateTargetedExamples(normalizedWidgetTypes); const validationRules = this.generateValidationChecklist(normalizedWidgetTypes); const structureGuidance = this.generateStructureGuidance(normalizedWidgetTypes); console.error(`[WIDGET_REQUIREMENTS_V110] Success - Generated requirements for ${normalizedWidgetTypes.length} widgets`); return { success: true, data: { selectedWidgetTypes: normalizedWidgetTypes, specificRequirements: specificRequirements, targetedExamples: targetedExamples, validationRules: validationRules, structureGuidance: structureGuidance, jitGuidance: { approach: "Fail-fast Just-In-Time delivery with strict validation", principle: "Only provide requirements for validated widget types", benefit: "Precise, reliable requirements with comprehensive error handling", nextStep: "Use these requirements to structure your lesson data in validate_lesson_data", v110Enhancement: "Fail-fast validation ensures all widget types are supported and properly formatted" } }, metadata: { version: "1.1.0", timestamp: new Date().toISOString(), processingTime: Date.now() - this.processingStartTime.getTime(), widgetTypesProcessed: normalizedWidgetTypes.length, failFastValidation: "passed" } }; } catch (error) { console.error('[WIDGET_REQUIREMENTS_V110] FAIL-FAST ERROR:', error.message); return { success: false, error: { code: 'WIDGET_REQUIREMENTS_VALIDATION_FAILED', message: error.message, timestamp: new Date().toISOString(), processingTime: Date.now() - this.processingStartTime.getTime(), failFast: true, developmentMode: true, troubleshooting: { requiredInputStructure: { selectedWidgets: "object or array containing widget type information" }, supportedInputFormats: [ "Array of widget objects: [{ type: 'head-1', count: 1 }, ...]", "Array of strings: ['head-1', 'text-1', 'quiz-1']", "Object with selectedWidgetTypes array: { selectedWidgetTypes: [...] }", "Object with widgets array: { widgets: [...] }", "Analysis result format: { data: { selectedWidgetTypes: [...] } }" ], supportedWidgetTypes: this.supportedWidgetTypes, validationChecks: [ "selectedWidgets must exist and not be null/undefined", "Must contain at least one valid widget type", "All widget types must be from supported list", "Widget type format must be valid (e.g., 'head-1', not 'head')" ], commonIssues: [ "Missing selectedWidgets parameter", "Empty widget array or object", "Unsupported widget type names", "Incorrect widget type format", "Nested data structure not properly accessed" ], debugSteps: [ "1. Check if input.selectedWidgets exists", "2. Verify widget types are from supported list", "3. Check widget type format (type-number pattern)", "4. Ensure array/object is not empty", "5. Verify data structure matches expected format" ] } } }; } } /** * COMPREHENSIVE FAIL-FAST VALIDATION for selectedWidgets * No fallbacks - strict validation with detailed error reporting */ validateSelectedWidgetsComprehensively(input) { const errors = []; // Basic input structure validation if (!input || typeof input !== 'object') { errors.push("❌ INPUT STRUCTURE ERROR:\nInput must be an object containing selectedWidgets.\nReceived: " + typeof input + "\nRequired format: { selectedWidgets: [...] }"); return { valid: false, errors }; } // selectedWidgets existence validation if (!input.selectedWidgets) { errors.push("❌ MISSING SELECTEDWIDGETS ERROR:\nselectedWidgets parameter is required.\nThis should come from the analyze_content_for_widgets tool output.\nRequired format: { selectedWidgets: [...] }"); return { valid: false, errors }; } if (input.selectedWidgets === null || input.selectedWidgets === undefined) { errors.push("❌ NULL SELECTEDWIDGETS ERROR:\nselectedWidgets cannot be null or undefined.\nEnsure analyze_content_for_widgets completed successfully.\nCheck previous tool output for errors."); return { valid: false, errors }; } // Extract and validate widget types const extractionResult = this.extractAndValidateWidgetTypes(input.selectedWidgets); if (!extractionResult.valid) { errors.push(...extractionResult.errors); return { valid: false, errors }; } const { normalizedWidgetTypes } = extractionResult; // Validate extracted widget types const widgetValidationResult = this.validateIndividualWidgetTypes(normalizedWidgetTypes); if (!widgetValidationResult.valid) { errors.push(...widgetValidationResult.errors); return { valid: false, errors }; } console.error(`[WIDGET_REQUIREMENTS_V110] Validation passed - ${normalizedWidgetTypes.length} valid widget types`); return { valid: true, normalizedWidgetTypes, errors: [] }; } /** * EXTRACT AND VALIDATE widget types from various input formats */ extractAndValidateWidgetTypes(selectedWidgets) { const errors = []; let widgetTypes = []; try { // Handle array format if (Array.isArray(selectedWidgets)) { if (selectedWidgets.length === 0) { errors.push("❌ EMPTY ARRAY ERROR:\nselectedWidgets array is empty.\nEnsure analyze_content_for_widgets generated widget recommendations.\nArray should contain widget type objects or strings."); return { valid: false, errors }; } // Array of widget objects: [{ type: "head-1", ... }, ...] if (selectedWidgets.every(item => typeof item === 'object' && item.type)) { widgetTypes = selectedWidgets.map(item => item.type); } // Array of strings: ["head-1", "text-1", ...] else if (selectedWidgets.every(item => typeof item === 'string')) { widgetTypes = selectedWidgets; } // Mixed or invalid array else { errors.push(`❌ INVALID ARRAY FORMAT ERROR:\nselectedWidgets array contains invalid items.\nExpected: Array of objects with 'type' property OR array of strings\nReceived: ${selectedWidgets.map(item => typeof item).join(', ')}\nFirst item: ${JSON.stringify(selectedWidgets[0], null, 2)}`); return { valid: false, errors }; } } // Handle object format else if (typeof selectedWidgets === 'object') { // Check for nested data structure (from analyze_content_for_widgets) if (selectedWidgets.data && selectedWidgets.data.selectedWidgetTypes) { if (!Array.isArray(selectedWidgets.data.selectedWidgetTypes)) { errors.push("❌ NESTED DATA FORMAT ERROR:\nselectedWidgets.data.selectedWidgetTypes must be an array.\nThis appears to be output from analyze_content_for_widgets tool.\nCheck if the tool completed successfully."); return { valid: false, errors }; } const nestedTypes = selectedWidgets.data.selectedWidgetTypes; if (nestedTypes.length === 0) { errors.push("❌ EMPTY NESTED ARRAY ERROR:\nselectedWidgets.data.selectedWidgetTypes is empty.\nanalyze_content_for_widgets should have generated widget recommendations.\nCheck previous tool output for errors."); return { valid: false, errors }; } // Extract type from nested objects widgetTypes = nestedTypes.map(item => { if (typeof item === 'object' && item.type) { return item.type; } else if (typeof item === 'string') { return item; } else { throw new Error(`Invalid widget type format in nested data: ${JSON.stringify(item)}`); } }); } // Direct selectedWidgetTypes property else if (selectedWidgets.selectedWidgetTypes) { if (!Array.isArray(selectedWidgets.selectedWidgetTypes)) { errors.push("❌ SELECTEDWIDGETTYPES FORMAT ERROR:\nselectedWidgets.selectedWidgetTypes must be an array.\nReceived: " + typeof selectedWidgets.selectedWidgetTypes); return { valid: false, errors }; } widgetTypes = selectedWidgets.selectedWidgetTypes.map(item => { if (typeof item === 'object' && item.type) { return item.type; } else if (typeof item === 'string') { return item; } else { throw new Error(`Invalid widget type format: ${JSON.stringify(item)}`); } }); } // widgets property else if (selectedWidgets.widgets) { if (!Array.isArray(selectedWidgets.widgets)) { errors.push("❌ WIDGETS PROPERTY FORMAT ERROR:\nselectedWidgets.widgets must be an array.\nReceived: " + typeof selectedWidgets.widgets); return { valid: false, errors }; } widgetTypes = selectedWidgets.widgets.map(item => { if (typeof item === 'object' && item.type) { return item.type; } else if (typeof item === 'string') { return item; } else { throw new Error(`Invalid widget format: ${JSON.stringify(item)}`); } }); } // Object with direct widget type properties else { const keys = Object.keys(selectedWidgets); if (keys.length === 0) { errors.push("❌ EMPTY OBJECT ERROR:\nselectedWidgets object is empty.\nObject should contain widget type information.\nCheck analyze_content_for_widgets output."); return { valid: false, errors }; } errors.push(`❌ UNRECOGNIZED OBJECT FORMAT ERROR:\nselectedWidgets object format not recognized.\nReceived keys: ${keys.join(', ')}\nExpected keys: 'data', 'selectedWidgetTypes', 'widgets'\nObject: ${JSON.stringify(selectedWidgets, null, 2)}`); return { valid: false, errors }; } } // Handle string format (single widget) else if (typeof selectedWidgets === 'string') { widgetTypes = [selectedWidgets]; } // Invalid format else { errors.push(`❌ INVALID TYPE ERROR:\nselectedWidgets must be an array, object, or string.\nReceived: ${typeof selectedWidgets}\nValue: ${JSON.stringify(selectedWidgets, null, 2)}`); return { valid: false, errors }; } } catch (extractionError) { errors.push(`❌ EXTRACTION ERROR:\nFailed to extract widget types: ${extractionError.message}\nInput structure may be malformed or unsupported.\nCheck analyze_content_for_widgets output format.`); return { valid: false, errors }; } // Final validation if (widgetTypes.length === 0) { errors.push("❌ NO WIDGET TYPES EXTRACTED ERROR:\nNo valid widget types found after processing.\nInput may be malformed or empty.\nEnsure analyze_content_for_widgets generated valid recommendations."); return { valid: false, errors }; } // Remove duplicates and filter out empty/null values const normalizedWidgetTypes = [...new Set(widgetTypes)].filter(type => type && typeof type === 'string' && type.trim().length > 0); if (normalizedWidgetTypes.length === 0) { errors.push("❌ NO VALID WIDGET TYPES ERROR:\nAll extracted widget types were empty, null, or invalid.\nExtracted types: " + JSON.stringify(widgetTypes)); return { valid: false, errors }; } return { valid: true, normalizedWidgetTypes, errors: [] }; } /** * VALIDATE individual widget types against supported list */ validateIndividualWidgetTypes(widgetTypes) { const errors = []; const invalidTypes = []; const unsupportedTypes = []; widgetTypes.forEach(type => { // Check format (should be like "head-1", "text-1", etc.) if (!type.match(/^[a-z-]+(-\d+)?$/)) { invalidTypes.push(type); return; } // Check if supported if (!this.supportedWidgetTypes.includes(type)) { unsupportedTypes.push(type); } }); if (invalidTypes.length > 0) { errors.push(`❌ INVALID WIDGET TYPE FORMAT ERROR:\nThese widget types have invalid format: ${invalidTypes.join(', ')}\nValid format examples: 'head-1', 'text-1', 'quiz-1'\nWidget types should follow 'name-number' pattern.`); } if (unsupportedTypes.length > 0) { errors.push(`❌ UNSUPPORTED WIDGET TYPES ERROR:\nThese widget types are not supported: ${unsupportedTypes.join(', ')}\n\nSupported widget types:\n${this.supportedWidgetTypes.map(t => ` • ${t}`).join('\n')}\n\nCheck widget type spelling and ensure they're from the supported list.`); } if (errors.length > 0) { return { valid: false, errors }; } return { valid: true, errors: [] }; } /** * Get specific requirements for validated widget types */ getSpecificRequirements(widgetTypes) { const requirements = {}; widgetTypes.forEach(widgetType => { const requirement = this.requirementsDatabase[widgetType]; if (requirement) { requirements[widgetType] = { ...requirement, validated: true, failFastNote: "All requirements are mandatory - missing fields will cause validation errors" }; } else { // This should not happen after validation, but fail fast if it does throw new Error(`Requirements not found for validated widget type: ${widgetType}. This indicates a system error.`); } }); return requirements; } /** * Generate targeted examples for validated widgets */ generateTargetedExamples(widgetTypes) { const examples = {}; widgetTypes.forEach(widgetType => { const baseType = widgetType.split('-')[0]; examples[widgetType] = this.getExampleForWidgetType(baseType, widgetType); }); return examples; } /** * Generate validation checklist for widgets */ generateValidationChecklist(widgetTypes) { const rules = { general: [ "All widgets must have unique IDs", "Widget content must not be empty", "All required fields must be present", "Content must be educationally appropriate" ], specific: {} }; widgetTypes.forEach(widgetType => { const baseType = widgetType.split('-')[0]; rules.specific[widgetType] = this.getValidationRulesForType(baseType); }); return rules; } /** * Generate structure guidance based on widgets */ generateStructureGuidance(widgetTypes) { const hasAssessment = widgetTypes.some(type => type.startsWith('quiz-') || type.startsWith('flashcards-')); const hasText = widgetTypes.some(type => type.startsWith('text-')); const hasMedia = widgetTypes.some(type => type.startsWith('image-') || type.startsWith('video-')); return { requiredFormat: { metadata: { topic: "string - lesson topic/title", gradeLevel: "string - target grade level", subject: "string - subject area", duration: "string - estimated duration" }, widgets: "array - one object per widget with all required fields" }, recommendations: { structure: hasText ? "Include substantial text content" : "Consider adding explanatory content", assessment: hasAssessment ? "Include complete assessment content" : "Consider adding assessment widgets", media: hasMedia ? "Provide media descriptions or content" : "Consider visual enhancements" }, failFastWarnings: [ "All widget fields are validated strictly", "Missing required fields will cause immediate failure", "Content validation is comprehensive - ensure quality", "Assessment widgets require complete questions and answers" ] }; } /** * Get example for specific widget type */ getExampleForWidgetType(baseType, fullType) { const examples = { 'head': { id: "widget_001", type: fullType, content: { title: "Lesson Title", subtitle: "Educational objective" } }, 'text': { id: "widget_002", type: fullType, content: { text: "Educational content with detailed explanations...", title: "Section Title" } }, 'quiz': { id: "widget_003", type: fullType, content: { questions: [ { question: "What is the main concept?", options: ["Option A", "Option B", "Option C", "Option D"], correct: 0 } ] } }, 'flashcards': { id: "widget_004", type: fullType, content: { cards: [ { front: "Term or Question", back: "Definition or Answer" } ] } }, 'image': { id: "widget_005", type: fullType, content: { src: "image_url_or_description", alt: "Alternative text description", caption: "Image caption" } }, 'list': { id: "widget_006", type: fullType, content: { items: ["Item 1", "Item 2", "Item 3"], title: "List Title" } } }; return examples[baseType] || { id: "widget_xxx", type: fullType, content: "Content structure varies by widget type" }; } /** * Get validation rules for widget type */ getValidationRulesForType(baseType) { const rules = { 'head': ["Title must be present", "Content should be concise"], 'text': ["Text content must not be empty", "Should be educationally substantial"], 'quiz': ["Must have at least one question", "All questions need options and correct answers"], 'flashcards': ["Must have at least one card", "Front and back content required"], 'image': ["Image source or description required", "Alt text mandatory"], 'list': ["Must have at least one item", "Items should be meaningful"] }; return rules[baseType] || ["Standard content validation applies"]; } /** * Get supported widget types */ 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' ]; } /** * Initialize requirements database */ initializeRequirementsDatabase() { return { 'head-1': { description: "Professional lesson header", requiredFields: ["id", "type", "content"], contentStructure: { title: "string - main lesson title", subtitle: "string (optional) - lesson subtitle or objective" }, validation: ["title must not be empty", "id must be unique"] }, 'text-1': { description: "Main explanatory content widget", requiredFields: ["id", "type", "content"], contentStructure: { text: "string - main educational content", title: "string (optional) - section title" }, validation: ["text must not be empty", "content should be educational"] }, 'quiz-1': { description: "Interactive quiz widget", requiredFields: ["id", "type", "content"], contentStructure: { questions: "array - quiz questions with options and answers" }, validation: ["must have at least one question", "questions need options and correct answers"] }, 'flashcards-1': { description: "Vocabulary flashcards widget", requiredFields: ["id", "type", "content"], contentStructure: { cards: "array - flashcard terms and definitions" }, validation: ["must have at least one card", "cards need front and back content"] }, 'image-1': { description: "Educational image widget", requiredFields: ["id", "type", "content"], contentStructure: { src: "string - image URL or description", alt: "string - alternative text", caption: "string (optional) - image caption" }, validation: ["image source required", "alt text mandatory"] }, 'list-1': { description: "Structured list widget", requiredFields: ["id", "type", "content"], contentStructure: { items: "array - list items", title: "string (optional) - list title" }, validation: ["must have at least one item", "items should be meaningful"] } }; } } /** * Create and export the tool instance for JIT server integration */ export function createWidgetRequirementsToolV110() { const requirementsTool = new WidgetRequirementsToolV110(); return { name: 'get_widget_requirements_v110', description: 'STEP 3 v1.1.0: Get precise API requirements for selected widgets with fail-fast validation. Provides comprehensive validation and detailed error reporting for development.', inputSchema: { type: 'object', properties: { selectedWidgets: { type: 'object', description: 'Selected widget types from analyze_content_for_widgets output (various formats supported)' } }, required: ['selectedWidgets'] }, handler: async (input) => { return await requirementsTool.getWidgetRequirements(input); } }; }

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