Skip to main content
Glama
browser-automation-api-multi-tool-v5.0.0.js16.3 kB
#!/usr/bin/env node /** * EuConquisto Composer Multi-Tool MCP Server * @version 5.0.0-multi-tool * @description Specialized tools architecture for precise error isolation and debugging */ import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ListToolsRequestSchema, ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js'; import { chromium } from 'playwright'; import { readFileSync } from 'fs'; import { join } from 'path'; // Multi-Tool Architecture Components import { createClaudeGuidedComposer } from './claude-guided-composer.js'; import { createLessonDataValidator } from '../src/tools/validate-lesson-data.js'; import { createComposerFormatter } from '../src/tools/format-for-composer.js'; import { createCompositionAPISaver } from '../src/tools/save-composition-api.js'; import { createCompositionEditorOpener } from '../src/tools/open-composition-editor.js'; const PROJECT_ROOT = '/Users/ricardokawasaki/Desktop/euconquisto-composer-mcp-poc'; class EuConquistoMultiToolServer { constructor() { this.server = new Server( { name: 'euconquisto-composer-multi-tool', version: '5.0.0-multi-tool', }, { capabilities: { tools: {}, }, } ); // Initialize multi-tool components this.claudeGuidedComposer = createClaudeGuidedComposer(); this.lessonDataValidator = createLessonDataValidator(); this.composerFormatter = createComposerFormatter(); this.compositionAPISaver = createCompositionAPISaver(); this.compositionEditorOpener = createCompositionEditorOpener(); console.error('[INIT] Multi-Tool Architecture initialized - 5 specialized tools ready'); this.jwtToken = null; this.loadJwtToken(); this.setupHandlers(); } loadJwtToken() { try { const tokenPath = join(PROJECT_ROOT, 'archive/authentication/correct-jwt-new.txt'); this.jwtToken = readFileSync(tokenPath, 'utf-8').trim(); } catch (error) { try { const fallbackPath = join(PROJECT_ROOT, 'correct-jwt-new.txt'); this.jwtToken = readFileSync(fallbackPath, 'utf-8').trim(); } catch (fallbackError) { // Silent fail - will be handled in individual tools } } } setupHandlers() { this.server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: [ { name: 'get_lesson_guidance', description: 'STEP 1: Get comprehensive guidance for creating educational content. Provides widget options and pedagogical frameworks. Use this before creating lesson content.', inputSchema: { type: 'object', properties: { prompt: { type: 'string', description: 'Educational content prompt or topic' }, subject: { type: 'string', description: 'Subject area', enum: ['Ciências', 'Matemática', 'História', 'Português', 'Geografia', 'Arte', 'Educação Física', 'Inglês'] }, gradeLevel: { type: 'string', description: 'Grade level (e.g., 6º ano)' } }, required: ['prompt'] } }, { name: 'validate_lesson_data', description: 'STEP 2: Validate lesson data structure and content. Comprehensive validation with 68+ rules covering all widget types and educational standards.', inputSchema: { type: 'object', properties: { lessonData: { type: 'object', description: 'Complete lesson data with metadata and widgets array' } }, required: ['lessonData'] } }, { name: 'format_for_composer', description: 'STEP 3: Transform validated lesson data into Composer JSON format. Applies educational intelligence and subject-specific optimizations.', inputSchema: { type: 'object', properties: { validatedLessonData: { type: 'object', description: 'Validated lesson data from validate_lesson_data tool' } }, required: ['validatedLessonData'] } }, { name: 'save_composition_api', description: 'STEP 4: Save formatted composition to EuConquisto Composer via API. Enhanced error reporting with detailed diagnostics.', inputSchema: { type: 'object', properties: { composerJSON: { type: 'object', description: 'Composer-formatted JSON from format_for_composer tool' } }, required: ['composerJSON'] } }, { name: 'open_composition_editor', description: 'STEP 5: Navigate to saved composition in Composer editor. Comprehensive navigation debugging and health assessment.', inputSchema: { type: 'object', properties: { compositionUid: { type: 'string', description: 'Composition UID from save_composition_api tool' } }, required: ['compositionUid'] } } ] })); this.server.setRequestHandler(CallToolRequestSchema, async (request) => { switch (request.params.name) { case 'get_lesson_guidance': return this.provideLessonGuidance(request.params.arguments); case 'validate_lesson_data': return this.validateLessonData(request.params.arguments); case 'format_for_composer': return this.formatForComposer(request.params.arguments); case 'save_composition_api': return this.saveCompositionAPI(request.params.arguments); case 'open_composition_editor': return this.openCompositionEditor(request.params.arguments); case 'create_educational_composition': return { content: [ { type: 'text', text: JSON.stringify({ success: false, error: { code: 'DEPRECATED_TOOL', message: 'create_educational_composition is deprecated. Use the multi-tool workflow: validate_lesson_data → format_for_composer → save_composition_api → open_composition_editor' } }, null, 2) } ] }; default: throw new McpError( ErrorCode.MethodNotFound, `Unknown tool: ${request.params.name}. Available tools: get_lesson_guidance, validate_lesson_data, format_for_composer, save_composition_api, open_composition_editor` ); } }); } /** * STEP 1: Provide lesson guidance (unchanged from original) */ async provideLessonGuidance(args) { const { prompt, subject = 'Ciências', gradeLevel = '7º ano' } = args || {}; console.error('[GUIDANCE] Providing lesson guidance for multi-tool workflow'); const guidance = await this.claudeGuidedComposer.provideLessonGuidance(prompt, subject, gradeLevel); // Update guidance to instruct multi-tool workflow guidance.workflow = { description: 'Multi-tool workflow for precise error isolation', steps: [ '1. Use validate_lesson_data to validate your lesson structure', '2. Use format_for_composer to transform to Composer format', '3. Use save_composition_api to save via API', '4. Use open_composition_editor to navigate to the composition' ], benefits: [ 'Precise error isolation at each step', 'Detailed debugging information', 'Ability to retry individual failed steps', 'Enhanced educational intelligence' ] }; return { content: [ { type: 'text', text: JSON.stringify(guidance, null, 2) } ] }; } /** * STEP 2: Validate lesson data */ async validateLessonData(args) { const { lessonData } = args || {}; console.error('[VALIDATE] Starting lesson data validation'); if (!lessonData) { return { content: [ { type: 'text', text: JSON.stringify({ success: false, error: { code: 'MISSING_LESSON_DATA', message: 'No lesson data provided for validation' } }, null, 2) } ] }; } try { const validationResult = await this.lessonDataValidator.validateLessonData(lessonData); return { content: [ { type: 'text', text: JSON.stringify(validationResult, null, 2) } ] }; } catch (error) { console.error('[VALIDATE] Validation error:', error.message); return { content: [ { type: 'text', text: JSON.stringify({ success: false, error: { code: 'VALIDATION_SYSTEM_ERROR', message: error.message } }, null, 2) } ] }; } } /** * STEP 3: Format for Composer */ async formatForComposer(args) { const { validatedLessonData } = args || {}; console.error('[FORMAT] Starting Composer formatting with educational intelligence'); if (!validatedLessonData) { return { content: [ { type: 'text', text: JSON.stringify({ success: false, error: { code: 'MISSING_VALIDATED_DATA', message: 'No validated lesson data provided for formatting' } }, null, 2) } ] }; } try { const formatResult = await this.composerFormatter.formatForComposer(validatedLessonData); return { content: [ { type: 'text', text: JSON.stringify(formatResult, null, 2) } ] }; } catch (error) { console.error('[FORMAT] Formatting error:', error.message); return { content: [ { type: 'text', text: JSON.stringify({ success: false, error: { code: 'FORMAT_SYSTEM_ERROR', message: error.message } }, null, 2) } ] }; } } /** * STEP 4: Save composition via API */ async saveCompositionAPI(args) { const { composerJSON } = args || {}; console.error('[API_SAVE] Starting API save with enhanced debugging'); if (!composerJSON) { return { content: [ { type: 'text', text: JSON.stringify({ success: false, error: { code: 'MISSING_COMPOSER_JSON', message: 'No Composer JSON provided for API save' } }, null, 2) } ] }; } if (!this.jwtToken) { return { content: [ { type: 'text', text: JSON.stringify({ success: false, error: { code: 'MISSING_JWT_TOKEN', message: 'JWT token not found. Please ensure JWT token file exists.' } }, null, 2) } ] }; } let browser; try { // Launch browser for API save browser = await chromium.launch({ headless: false, slowMo: 100 }); const context = await browser.newContext({ viewport: { width: 1920, height: 1080 } }); const page = await context.newPage(); // Set up console logging page.on('console', msg => { console.error(`[BROWSER CONSOLE] ${msg.text()}`); }); // Authenticate via JWT redirect server console.error('[API_SAVE] Authenticating via JWT redirect server'); await page.goto('http://localhost:8080', { waitUntil: 'networkidle', timeout: 30000 }); await page.waitForURL('**/composer.euconquisto.com/**', { timeout: 15000 }); await page.waitForLoadState('networkidle'); await page.waitForTimeout(2000); // Use specialized API saver tool const saveResult = await this.compositionAPISaver.saveCompositionAPI(composerJSON, page); // Keep browser open for next step // Don't close browser - it will be used by open_composition_editor return { content: [ { type: 'text', text: JSON.stringify(saveResult, null, 2) } ] }; } catch (error) { console.error('[API_SAVE] Save error:', error.message); if (browser) { // Keep browser open for debugging } return { content: [ { type: 'text', text: JSON.stringify({ success: false, error: { code: 'API_SAVE_SYSTEM_ERROR', message: error.message } }, null, 2) } ] }; } } /** * STEP 5: Open composition editor */ async openCompositionEditor(args) { const { compositionUid } = args || {}; console.error(`[EDITOR_OPEN] Starting editor navigation for UID: ${compositionUid}`); if (!compositionUid) { return { content: [ { type: 'text', text: JSON.stringify({ success: false, error: { code: 'MISSING_COMPOSITION_UID', message: 'No composition UID provided for editor navigation' } }, null, 2) } ] }; } // For this step, we need to reuse an existing browser session // In a real implementation, we'd share browser instances between tools // For now, we'll create a new browser session let browser; try { browser = await chromium.launch({ headless: false, slowMo: 100 }); const context = await browser.newContext({ viewport: { width: 1920, height: 1080 } }); const page = await context.newPage(); // Authenticate first console.error('[EDITOR_OPEN] Authenticating for editor access'); await page.goto('http://localhost:8080', { waitUntil: 'networkidle', timeout: 30000 }); await page.waitForURL('**/composer.euconquisto.com/**', { timeout: 15000 }); await page.waitForLoadState('networkidle'); await page.waitForTimeout(2000); // Use specialized editor opener tool const openResult = await this.compositionEditorOpener.openCompositionEditor(compositionUid, page); // Keep browser open for user interaction console.error('[EDITOR_OPEN] Browser remains open for user interaction'); return { content: [ { type: 'text', text: JSON.stringify(openResult, null, 2) } ] }; } catch (error) { console.error('[EDITOR_OPEN] Editor opening error:', error.message); return { content: [ { type: 'text', text: JSON.stringify({ success: false, error: { code: 'EDITOR_OPEN_SYSTEM_ERROR', message: error.message } }, null, 2) } ] }; } } async run() { const transport = new StdioServerTransport(); await this.server.connect(transport); } } const server = new EuConquistoMultiToolServer(); server.run().catch(console.error);

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