browser-automation-api-multi-tool-v5.0.0.js•16.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);