Skip to main content
Glama
get-widget-requirements.js29.7 kB
#!/usr/bin/env node /** * Widget Requirements Tool v5.2.0 - FULLY OPERATIONAL * Just-In-Time approach: Provides precise API requirements for selected widgets only * @version 5.2.0 (January 12, 2025) * @status FULLY OPERATIONAL - Precise JIT requirements delivery * @reference JIT workflow step 3 of 7 * @milestone v5.2.0 - Token-efficient widget-specific requirements */ export class WidgetRequirementsTool { constructor() { this.processingStartTime = null; this.requirementsDatabase = this.initializeRequirementsDatabase(); } /** * Main requirements entry point for JIT workflow * @param {Object} input - Contains selectedWidgets (array or object with widget types) * @returns {Object} Precise API requirements for selected widgets only */ async getWidgetRequirements(input) { this.processingStartTime = new Date(); try { const { selectedWidgets } = input; // Validate input if (!selectedWidgets) { throw new Error('selectedWidgets is required'); } // Normalize widget types from different input formats const widgetTypes = this.extractWidgetTypes(selectedWidgets); if (widgetTypes.length === 0) { const supportedFormats = [ 'Array of widget objects: [{ type: "head-1", count: 1 }, ...]', 'Array of strings: ["head-1", "text-1", "quiz-1"]', 'Object with widgets array: { widgets: [...] }', 'Object with selectedWidgetTypes: { selectedWidgetTypes: [...] }', 'Single string: "head-1"' ]; throw new Error(`No valid widget types found in selectedWidgets. Supported formats: ${supportedFormats.join('\n')} Received: ${JSON.stringify(selectedWidgets, null, 2)}`); } // Get specific requirements for each widget type const specificRequirements = this.getSpecificRequirements(widgetTypes); // Generate targeted examples const targetedExamples = this.generateTargetedExamples(widgetTypes); // Create validation rules const validationRules = this.generateValidationChecklist(widgetTypes); // Create JIT requirements response const requirements = { success: true, data: { selectedWidgetTypes: widgetTypes, specificRequirements: specificRequirements, targetedExamples: targetedExamples, validationRules: validationRules, jitGuidance: { approach: "Token-efficient Just-In-Time delivery", principle: "Only provide requirements for widgets you will actually use", benefit: "Precise, relevant requirements without overwhelming detail", nextStep: "Use these requirements to structure your lesson data in validate_lesson_data" }, lessonStructureGuidance: { requiredFormat: { metadata: { topic: "string - lesson topic/title", gradeLevel: "string - target grade level", subject: "string - subject area", learningObjectives: "array - learning goals" }, widgets: "array - widget objects with type and content matching requirements above" }, criticalReminder: "Structure your lesson data exactly as shown - this format is required for validate_lesson_data tool" }, commonPitfalls: this.getCommonPitfalls(widgetTypes), qualityTips: this.getQualityTips(widgetTypes) }, debug: { timestamp: new Date().toISOString(), processingTime: Date.now() - this.processingStartTime.getTime(), widgetTypesProcessed: widgetTypes.length, requirementsGenerated: Object.keys(specificRequirements).length } }; return requirements; } catch (error) { console.error('[WIDGET_REQUIREMENTS] Error:', error.message); return { success: false, error: { code: 'REQUIREMENTS_ERROR', message: error.message, timestamp: new Date().toISOString() } }; } } /** * Extract widget types from various input formats * ENHANCED: Now handles multiple flexible parameter formats */ extractWidgetTypes(selectedWidgets) { let widgetTypes = []; console.error('[WIDGET_REQUIREMENTS] Extracting widget types from:', typeof selectedWidgets); console.error('[WIDGET_REQUIREMENTS] Input structure:', JSON.stringify(selectedWidgets, null, 2)); // Handle direct array format: [{ type: "head-1", count: 1, ... }, ...] if (Array.isArray(selectedWidgets)) { widgetTypes = selectedWidgets.map(item => { if (typeof item === 'string') return item; if (item && typeof item === 'object' && item.type) return item.type; return null; }).filter(Boolean); } // Handle nested object with widgets array: { widgets: [...] } else if (selectedWidgets.widgets && Array.isArray(selectedWidgets.widgets)) { widgetTypes = selectedWidgets.widgets.map(item => typeof item === 'string' ? item : item.type ).filter(Boolean); } // Handle object with selectedWidgetTypes array else if (selectedWidgets.selectedWidgetTypes && Array.isArray(selectedWidgets.selectedWidgetTypes)) { widgetTypes = selectedWidgets.selectedWidgetTypes.map(item => typeof item === 'string' ? item : item.type ).filter(Boolean); } // Handle object with widgetTypes array else if (selectedWidgets.widgetTypes && Array.isArray(selectedWidgets.widgetTypes)) { widgetTypes = selectedWidgets.widgetTypes.filter(type => typeof type === 'string'); } // Handle mapping rationale structure (from analyze_content_for_widgets output) else if (selectedWidgets.mappingRationale && selectedWidgets.mappingRationale.widgetTypes) { widgetTypes = selectedWidgets.mappingRationale.widgetTypes; } // Handle single widget type else if (typeof selectedWidgets === 'string') { widgetTypes = [selectedWidgets]; } // Handle object with direct widget type properties else if (selectedWidgets && typeof selectedWidgets === 'object') { // Try to extract from any property that looks like it contains widget types Object.keys(selectedWidgets).forEach(key => { const value = selectedWidgets[key]; if (Array.isArray(value)) { value.forEach(item => { if (typeof item === 'string' && this.isValidWidgetType(item)) { widgetTypes.push(item); } else if (item && item.type && this.isValidWidgetType(item.type)) { widgetTypes.push(item.type); } }); } }); } // Remove duplicates and ensure valid types const uniqueTypes = [...new Set(widgetTypes)].filter(type => this.isValidWidgetType(type)); console.error('[WIDGET_REQUIREMENTS] Extracted widget types:', uniqueTypes); return uniqueTypes; } /** * Get common pitfalls for selected widget types */ getCommonPitfalls(widgetTypes) { const pitfalls = []; if (widgetTypes.includes('quiz-1')) { pitfalls.push({ widget: "quiz-1", pitfall: "Using 'answers' instead of 'options'", solution: "Use 'options' array in your content - API will convert to 'answers'" }); } if (widgetTypes.includes('flashcards-1')) { pitfalls.push({ widget: "flashcards-1", pitfall: "Inconsistent field names", solution: "Use 'question'/'answer' OR 'front'/'back' consistently" }); } if (widgetTypes.includes('text-1')) { pitfalls.push({ widget: "text-1", pitfall: "Plain text instead of HTML", solution: "Wrap content in HTML tags like <p>, <h2>, <ul>, etc." }); } return pitfalls; } /** * Get quality tips for selected widget types */ getQualityTips(widgetTypes) { const tips = []; if (widgetTypes.includes('head-1')) { tips.push("Always start lessons with head-1 widget for professional appearance"); } if (widgetTypes.includes('flashcards-1')) { tips.push("Keep flashcard questions short and answers comprehensive but concise"); } if (widgetTypes.includes('quiz-1')) { tips.push("Include plausible distractors in quiz options to test real understanding"); } if (widgetTypes.includes('text-1')) { tips.push("Structure text content with headings, paragraphs, and lists for better readability"); } if (widgetTypes.includes('image-1')) { tips.push("Choose high-quality, relevant images that enhance understanding"); } return tips; } /** * Get specific API requirements for widget types */ getSpecificRequirements(widgetTypes) { const specificReqs = {}; widgetTypes.forEach(type => { if (this.requirementsDatabase[type]) { specificReqs[type] = this.requirementsDatabase[type]; } }); return specificReqs; } /** * Initialize comprehensive widget requirements database */ initializeRequirementsDatabase() { return { 'head-1': { apiName: "head-1", description: "Professional lesson header - always use as first widget", requiredFields: { category: { type: "string", format: "Plain text or HTML", example: "\"CIÊNCIAS\" or \"<p>BIOLOGIA</p>\"", apiFieldName: "category" }, author_name: { type: "string", format: "Plain text or HTML", example: "\"Professor João\" or \"<p>Professor João</p>\"", apiFieldName: "author_name" } }, optionalFields: { author_office: { type: "string", format: "Plain text or HTML", example: "\"Especialista em Biologia\"", apiFieldName: "author_office" }, background_image: { type: "string", format: "Valid URL", example: "\"https://images.unsplash.com/photo-1234567890\"", apiFieldName: "background_image" }, avatar: { type: "string", format: "Valid URL", example: "\"https://ui-avatars.com/api/?name=Professor\"", apiFieldName: "avatar" } }, complexity: "low" }, 'text-1': { apiName: "text-1", description: "Rich HTML content display", requiredFields: { text: { type: "string", format: "HTML content, minimum 20 characters", example: "\"<h2>Título</h2><p>Conteúdo educacional detalhado...</p>\"", apiFieldName: "text", validation: "Must be at least 20 characters long" } }, optionalFields: { content_title: { type: "string", format: "Plain text title", example: "\"Introdução à Fotossíntese\"", apiFieldName: "content_title" }, background_color: { type: "string", format: "Hex color code", example: "\"#FFFFFF\"", apiFieldName: "background_color" } }, complexity: "medium" }, 'flashcards-1': { apiName: "flashcards-1", description: "Interactive flip cards for memorization", requiredFields: { flashcards_items: { type: "array", format: "Array of flashcard objects", apiFieldName: "flashcards_items", itemStructure: { question: { type: "string", format: "Front of card content", example: "\"Clorofila\"", apiFieldName: "question" }, answer: { type: "string", format: "Back of card content", example: "\"Pigmento verde que captura luz solar\"", apiFieldName: "answer" } }, alternativeFormat: { front: "Can use \"front\" instead of \"question\"", back: "Can use \"back\" instead of \"answer\"" } } }, complexity: "low" }, 'quiz-1': { apiName: "quiz-1", description: "Interactive multiple-choice quiz", requiredFields: { questions: { type: "array", format: "Array of question objects", apiFieldName: "questions", itemStructure: { question: { type: "string", format: "Question text, minimum 10 characters", example: "\"Qual é o principal produto da fotossíntese?\"", apiFieldName: "question" }, options: { type: "array", format: "Array of answer choices (will become \"answers\" in API)", example: "[\"Oxigênio\", \"Glicose\", \"Água\", \"CO2\"]", apiFieldName: "options", apiConversion: "Converted to \"answers\" field in API" }, correct_option: { type: "number", format: "Zero-based index of correct answer", example: "1 (for second option)", apiFieldName: "correct_option" } } } }, optionalFields: { max_attempts: { type: "number", format: "Number between 1-5", example: "3", apiFieldName: "max_attempts" } }, criticalNote: "Use \"options\" array in your content - API will convert to \"answers\"", complexity: "high" }, 'image-1': { apiName: "image-1", description: "Single image display", requiredFields: { image: { type: "string", format: "Valid image URL", example: "\"https://images.unsplash.com/photo-1234567890\"", apiFieldName: "image", validation: "Must be accessible image URL" } }, optionalFields: { caption: { type: "string", format: "Image description/caption", example: "\"Processo de fotossíntese em folhas verdes\"", apiFieldName: "caption" } }, complexity: "low" }, 'list-1': { apiName: "list-1", description: "Structured list presentation", requiredFields: { items: { type: "array", format: "Array of list items", example: "[\"Item 1\", \"Item 2\", \"Item 3\"]", apiFieldName: "items", validation: "Must have at least 2 items" } }, optionalFields: { content_title: { type: "string", format: "List title", example: "\"Tipos de Transferência de Calor\"", apiFieldName: "content_title" } }, complexity: "low" } }; } /** * Generate targeted examples for selected widgets only */ generateTargetedExamples(widgetTypes) { const examples = {}; widgetTypes.forEach(type => { examples[type] = this.createWidgetExample(type); }); return examples; } /** * Create widget-specific example content */ createWidgetExample(widgetType) { const exampleTemplates = { 'head-1': { description: 'Professional lesson header with required fields', example: { type: 'head-1', content: { category: 'CIÊNCIAS', author_name: 'Professor(a) Virtual', author_office: 'Especialista em Biologia' } }, notes: 'Always use as first widget. Category and author_name are required.' }, 'text-1': { description: 'HTML educational content with minimum 20 characters', example: { type: 'text-1', content: { text: '<h2>Título da Seção</h2><p>Conteúdo educacional detalhado explicando conceitos importantes. Mínimo de 20 caracteres necessário para validação.</p>' } }, notes: 'Use HTML formatting. Minimum 20 characters required.' }, 'quiz-1': { description: 'Multiple-choice quiz with options array and correct_option index', example: { type: 'quiz-1', content: { questions: [ { question: 'Qual é o principal produto da fotossíntese?', options: ['Oxigênio', 'Glicose', 'Água', 'CO2'], correct_option: 1 } ], max_attempts: 3 } }, notes: 'CRITICAL: Use "options" array (not "choices"). Use "correct_option" index (0-based).' }, 'flashcards-1': { description: 'Flip cards with question/answer format', example: { type: 'flashcards-1', content: { flashcards_items: [ { question: 'Clorofila', answer: 'Pigmento verde responsável por capturar a luz solar' } ] } }, notes: 'Use "flashcards_items" array with "question"/"answer" format.' }, 'list-1': { description: 'Structured list with items array', example: { type: 'list-1', content: { items: ['Capturar luz solar', 'Absorver CO2', 'Produzir glicose'], list_type: 'numbered' } }, notes: 'CRITICAL: Use "items" array (not "list_items"). Optional list_type: bullet, numbered, checkbox.' }, 'image-1': { description: 'Single image display with caption', example: { type: 'image-1', content: { image: 'https://images.unsplash.com/photo-1416879595882-3373a0480b5b?w=800', caption: 'Processo de fotossíntese em folhas verdes' } }, notes: 'Image URL must be valid and accessible. Caption is optional.' }, 'video-1': { description: 'Video content with valid URL', example: { type: 'video-1', content: { video: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ' } }, notes: 'Video URL must be valid YouTube, Vimeo, or direct video link.' }, 'gallery-1': { description: 'Multiple images in carousel format', example: { type: 'gallery-1', content: { slides: [ { image: 'https://images.unsplash.com/photo-1', caption: 'Primeira etapa do processo' }, { image: 'https://images.unsplash.com/photo-2', caption: 'Segunda etapa do processo' } ] } }, notes: 'Each slide needs image URL. Caption is optional.' }, 'hotspots-1': { description: 'Interactive image with clickable markers', example: { type: 'hotspots-1', content: { background_image: 'https://images.unsplash.com/photo-plant-cell', markers: [ { x: 50, y: 30, title: 'Cloroplasto', content: 'Organela responsável pela fotossíntese' } ] } }, notes: 'CRITICAL: Use numeric coordinates (50, 30) not strings ("50%"). API converts to percentages.' } }; return exampleTemplates[widgetType] || { description: `Example not available for ${widgetType}`, example: {}, notes: 'Refer to general API documentation' }; } /** * Create focused reminders for selected widgets */ createFocusedReminders(widgetTypes) { const allReminders = this.apiCatalog.getAPIRequirementsForClaude().criticalReminders; const focused = []; // Widget-specific critical reminders const widgetReminders = { 'quiz-1': 'Quiz questions: Use "options" array with "correct_option" index (API will convert to "answers")', 'list-1': 'List widgets: Use "items" array (API will convert to "list_items")', 'hotspots-1': 'Hotspots: Use numeric coordinates like 50, 30 (API will convert to "50%", "30%")', 'flashcards-1': 'Flashcards: Use "flashcards_items" array with "question"/"answer" or "front"/"back"', 'text-1': 'All text content: Minimum 20 characters for validation', 'image-1': 'Image URLs: Must be complete valid URLs starting with https://', 'video-1': 'Video URLs: Must be complete valid URLs starting with https://', 'gallery-1': 'Gallery slides: Each slide must have valid image URL', 'head-1': 'Header: category and author_name are required fields' }; widgetTypes.forEach(type => { if (widgetReminders[type]) { focused.push(widgetReminders[type]); } }); // Always include general reminders focused.push('URLs: Must be complete valid URLs starting with https://'); focused.push('Text content: Minimum 20 characters for all text fields'); return focused; } /** * Generate widget-specific implementation tips */ generateWidgetSpecificTips(widgetTypes) { const tips = {}; const tipDatabase = { 'head-1': [ 'Always use as the first widget in your lesson', 'Category should reflect the subject (e.g., "CIÊNCIAS", "MATEMÁTICA")', 'Author name can be simple like "Professor(a) Virtual"' ], 'text-1': [ 'Use HTML formatting for better presentation (<h2>, <p>, <strong>, <em>)', 'Break long content into multiple text widgets for better readability', 'Ensure minimum 20 characters for validation success' ], 'quiz-1': [ 'Use clear, unambiguous question wording', 'Provide 3-5 answer options for optimal cognitive load', 'Include distractors that test common misconceptions', 'Set max_attempts to 2-3 for optimal learning' ], 'flashcards-1': [ 'Keep question/answer pairs concise and focused', 'Use for vocabulary, definitions, and key concepts', 'Consider 5-15 cards per flashcard widget for optimal recall' ], 'list-1': [ 'Use for step-by-step procedures or feature lists', 'Choose appropriate list_type: bullet, numbered, or checkbox', 'Keep list items concise and parallel in structure' ], 'image-1': [ 'Use descriptive captions for accessibility', 'Ensure images are relevant to educational content', 'Consider image size and loading time' ], 'video-1': [ 'Ensure videos are educational and age-appropriate', 'Keep video length appropriate for attention span', 'Provide context before and after video content' ], 'gallery-1': [ 'Use for step-by-step visual processes', 'Ensure logical sequence in slide order', 'Write descriptive captions for each slide' ], 'hotspots-1': [ 'Use for complex diagrams requiring detailed exploration', 'Place markers strategically for optimal learning', 'Write clear, informative content for each hotspot' ] }; widgetTypes.forEach(type => { if (tipDatabase[type]) { tips[type] = tipDatabase[type]; } }); return tips; } /** * Get formatting guidance for selected widgets */ getFormattingGuidance(widgetTypes) { const guidance = { overview: 'Follow these format requirements for your selected widgets', fieldNameMappings: {}, dataTypeRequirements: {}, validationRules: {} }; // Field name mappings for widgets that need conversion const mappings = { 'quiz-1': 'Your "options" array becomes "answers" in API', 'list-1': 'Your "items" array becomes "list_items" in API', 'hotspots-1': 'Your numeric coordinates become percentage strings in API' }; widgetTypes.forEach(type => { if (mappings[type]) { guidance.fieldNameMappings[type] = mappings[type]; } }); // Data type requirements const dataTypes = { 'head-1': { category: 'string', author_name: 'string' }, 'text-1': { text: 'string (min 20 chars)' }, 'quiz-1': { questions: 'array', options: 'array of strings', correct_option: 'number (0-based index)' }, 'flashcards-1': { flashcards_items: 'array of objects' }, 'list-1': { items: 'array of strings' }, 'image-1': { image: 'string (valid URL)' }, 'video-1': { video: 'string (valid URL)' }, 'gallery-1': { slides: 'array of objects with image URLs' }, 'hotspots-1': { background_image: 'string (URL)', markers: 'array', x: 'number', y: 'number' } }; widgetTypes.forEach(type => { if (dataTypes[type]) { guidance.dataTypeRequirements[type] = dataTypes[type]; } }); return guidance; } /** * Generate validation checklist for selected widgets */ generateValidationChecklist(widgetTypes) { const checklist = {}; const validationRules = { 'head-1': [ '✓ Category field is present and non-empty', '✓ Author name field is present and non-empty' ], 'text-1': [ '✓ Text content is present', '✓ Text content has minimum 20 characters', '✓ HTML tags are properly formatted' ], 'quiz-1': [ '✓ Questions array is present and non-empty', '✓ Each question has "options" array (not "choices")', '✓ Each question has "correct_option" number', '✓ Options array has 2-6 items', '✓ Correct_option index is valid (0-based)' ], 'flashcards-1': [ '✓ flashcards_items array is present', '✓ Each item has question and answer fields', '✓ Items array has 2-20 cards' ], 'list-1': [ '✓ Items array is present (not "list_items")', '✓ Items array has 1-20 items', '✓ All items are non-empty strings' ], 'image-1': [ '✓ Image URL is present and valid', '✓ Image URL starts with https://', '✓ Caption is optional but recommended' ], 'video-1': [ '✓ Video URL is present and valid', '✓ Video URL starts with https://', '✓ Video URL is from supported platform' ], 'gallery-1': [ '✓ Slides array is present', '✓ Each slide has valid image URL', '✓ Slides array has 2-15 items' ], 'hotspots-1': [ '✓ Background image URL is valid', '✓ Markers array is present', '✓ Each marker has numeric x, y coordinates (not strings)', '✓ Each marker has title and content', '✓ Coordinates are between 0-100 range' ] }; widgetTypes.forEach(type => { if (validationRules[type]) { checklist[type] = validationRules[type]; } }); return checklist; } /** * Check if widget type is valid */ isValidWidgetType(type) { const validTypes = [ 'head-1', 'text-1', 'quiz-1', 'flashcards-1', 'image-1', 'video-1', 'list-1', 'gallery-1', 'hotspots-1' ]; return validTypes.includes(type); } /** * Build widget complexity mapping */ buildWidgetComplexity() { return { 'head-1': 'low', 'image-1': 'low', 'list-1': 'low', 'flashcards-1': 'low', 'text-1': 'medium', 'gallery-1': 'medium', 'video-1': 'medium', 'quiz-1': 'high', 'hotspots-1': 'high' }; } /** * Build format examples database */ buildFormatExamples() { return { fieldNameExamples: { quiz: 'options: ["A", "B", "C"], correct_option: 1', list: 'items: ["Item 1", "Item 2", "Item 3"]', hotspots: 'markers: [{x: 50, y: 30, title: "Title", content: "Content"}]' }, dataTypeExamples: { string: '"Example text content"', number: '42', array: '["item1", "item2", "item3"]', object: '{"key": "value"}', url: '"https://example.com/image.jpg"' } }; } } /** * Create and export the tool instance for JIT server integration */ export function createWidgetRequirementsTool() { const tool = new WidgetRequirementsTool(); return { name: 'get_widget_requirements', description: 'STEP 3: Get precise API requirements for selected widgets only. Just-in-time delivery of specific field formats.', inputSchema: { type: 'object', properties: { selectedWidgets: { type: 'object', description: 'Selected widget types from analyze_content_for_widgets tool or array of widget type strings' } }, required: ['selectedWidgets'] }, handler: async (input) => { return await tool.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